Saving these notes.
This commit is contained in:
248
documentation/GUIDE__AE_UI_Style_Guidelines.md
Normal file
248
documentation/GUIDE__AE_UI_Style_Guidelines.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Aether UI — Design System Style Guidelines
|
||||
> **Version:** 1.0 (2026-03-06)
|
||||
> **Author:** One Sky IT / Scott Idem
|
||||
> **Scope:** All Aether SvelteKit frontend components
|
||||
> **Related:** `AE__UI_Component_Patterns.md`, `ae-firefly.css`, `documentation/AE__Components.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. Design Philosophy
|
||||
|
||||
**"Shiny serenity, like a firefly."**
|
||||
|
||||
The Aether UI is calm, focused, and softly luminous. It must be immediately readable under conference-room lighting, at a glance, by presenters who are nervous and in a hurry. Staff in the Speaker Ready Room need scan-speed identity confirmation. Remote presenters uploading files from home need a clear, unambiguous interface that doesn't waste their time.
|
||||
|
||||
Core principles:
|
||||
- **Identity first.** The user's first question is always *"Am I in the right place?"* Answer it with the hero card — name, time, room — before anything else is shown.
|
||||
- **Progressive disclosure.** Admin fields (codes, IDs, passcodes) are hidden unless `edit_mode` is active.
|
||||
- **Theme-aware always.** Zero hardcoded colors. Every background, border, and text color must respond to light/dark mode and the active theme via CSS variables.
|
||||
- **Transitions, not pops.** Every interactive state change is smoothed with `transition-colors duration-200`.
|
||||
- **Section 508 / WCAG 2.1 AA** compliance is non-negotiable. Contrast ratios, focus indicators, ARIA labels, and screen-reader regions are required everywhere.
|
||||
|
||||
---
|
||||
|
||||
## 2. The AE_Firefly Theme
|
||||
|
||||
**App default since 2026-03-06.** Set in `ae_stores.ts` as `theme_name = 'AE_Firefly'`.
|
||||
File: `src/ae-firefly.css` | Activated by: `data-theme="AE_Firefly"`
|
||||
|
||||
| Role | Palette Name | Hue | Use Case |
|
||||
|---|---|---|---|
|
||||
| **Primary** | Luminescent Teal | ~184° | Primary actions, date/time chips, focus rings, anchor links |
|
||||
| **Secondary** | Warm Amber-Gold | ~90° | Secondary actions, copy/email buttons, soft highlights |
|
||||
| **Tertiary** | Night-Sky Indigo | ~277° | Location/room chips, depth accents |
|
||||
| **Surface** | Moonlit Slate | ~215–233° | All backgrounds — off-white (light) → midnight slate (dark) |
|
||||
| **Warning** | Amber semantic | n/a | Disabled/inactive records, "no results" states, code badges |
|
||||
| **Success** | Green semantic | n/a | Active/complete states, file count badges |
|
||||
| **Error** | Red semantic | n/a | Errors, disabled-presenter states |
|
||||
|
||||
### Contrast Guarantees
|
||||
- Body text (`surface-950` on `surface-50`): > 15:1 in light mode ✓
|
||||
- Primary buttons: ≥ 3:1 for interactive component threshold ✓
|
||||
- Designed using OKLCH perceptual lightness — not HSL estimates
|
||||
|
||||
---
|
||||
|
||||
## 3. Color Token Rules
|
||||
|
||||
### ✅ Always Use Theme Tokens
|
||||
|
||||
**Backgrounds / surfaces:**
|
||||
```
|
||||
bg-surface-50-900 ← card faces (light: near-white, dark: deep slate)
|
||||
bg-surface-100-900 ← inner sections, code blocks, secondary panels
|
||||
bg-surface-50-950 ← page-level containers
|
||||
bg-surface-200-800 ← skeleton pulse placeholders, dividers
|
||||
```
|
||||
|
||||
**Borders:**
|
||||
```
|
||||
border-surface-200-800 ← standard card/panel borders
|
||||
border-primary-500 ← focus rings (via focus-visible:ring)
|
||||
border-warning-500 ← warning/caution interactive zones
|
||||
border-surface-500/20 ← subtle section dividers
|
||||
```
|
||||
|
||||
**Primary color accents (teal):**
|
||||
```
|
||||
bg-primary-500/10 ← tinted chip background (time chips)
|
||||
text-primary-700 dark:text-primary-300 ← chip text with auto dark mode
|
||||
hover:text-primary-500 ← link hover color
|
||||
```
|
||||
|
||||
**Tertiary color accents (indigo):**
|
||||
```
|
||||
bg-tertiary-500/10 ← tinted chip background (room/location chips)
|
||||
text-tertiary-700 dark:text-tertiary-300
|
||||
```
|
||||
|
||||
**Skeleton presets:**
|
||||
```
|
||||
preset-tonal-warning ← "no results", disabled rows, code tag badges
|
||||
preset-tonal-primary ← primary action buttons
|
||||
preset-tonal-surface ← neutral/secondary actions, status badges
|
||||
preset-tonal-success ← success states, file count badges
|
||||
preset-tonal-error ← error/blocked states
|
||||
preset-filled-*-500 ← solid-fill buttons (hover state for tonal)
|
||||
```
|
||||
|
||||
**Warning/error semantic backgrounds:**
|
||||
```
|
||||
bg-warning-100 border border-warning-300 ← inline warning banners
|
||||
bg-error-100 border border-error-300 ← inline error banners
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Never Use These
|
||||
|
||||
| Forbidden | Reason | Replace With |
|
||||
|---|---|---|
|
||||
| `bg-gray-*` | Fixed color, breaks dark mode | `bg-surface-*` tokens |
|
||||
| `bg-neutral-*` | Same — fixed hue | `bg-surface-*` tokens |
|
||||
| `bg-white` | Light-mode only | `bg-surface-50-900` |
|
||||
| `text-gray-*` | Breaks dark mode | `opacity-60` on inherited text |
|
||||
| `text-neutral-*` | Same | `opacity-*` |
|
||||
| `border-gray-*` | Non-theme border | `border-surface-200-800` |
|
||||
| `bg-yellow-*`, `text-yellow-*` | Bypasses warning semantic | `preset-tonal-warning` |
|
||||
| `bg-red-*`, `text-red-*` | Bypasses error semantic | `bg-error-100` / `preset-tonal-error` |
|
||||
| `bg-white dark:bg-gray-800` | Manual light/dark pair | Let theme tokens handle it — remove entirely |
|
||||
| `text-gray-600 dark:text-gray-400` | Manual light/dark pair | `opacity-60` |
|
||||
| `rounded-container-token` | Skeleton v3 class | `rounded-xl` |
|
||||
| `variant-soft-*` | Skeleton v3 class | `preset-tonal-*` |
|
||||
| `variant-ghost-*` | Skeleton v3 class | `preset-ghost-*` |
|
||||
| `variant-ringed-*` | Skeleton v3 class | `preset-outlined-*` |
|
||||
| `variant-filled-*` | Skeleton v3 class | `preset-filled-*` |
|
||||
| `preset-filled-surface-300-700` | v3 dual-shade notation | `bg-surface-200-800` or `bg-surface-100-900` |
|
||||
| `preset-filled-surface-400-600` | v3 dual-shade notation | `bg-surface-100-900` |
|
||||
| `overflow-x-scroll` | Forces scrollbar visible | `overflow-x-auto` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Skeleton v3 → v4 Migration Reference
|
||||
|
||||
The app uses **Skeleton v4**. v3 classes produce wrong or zero styling and must be updated on sight.
|
||||
|
||||
| Skeleton v3 | Skeleton v4 Equivalent |
|
||||
|---|---|
|
||||
| `variant-soft-primary` | `preset-tonal-primary` |
|
||||
| `variant-soft-secondary` | `preset-tonal-secondary` |
|
||||
| `variant-soft-warning` | `preset-tonal-warning` |
|
||||
| `variant-soft-success` | `preset-tonal-success` |
|
||||
| `variant-ghost-success` | `preset-ghost-success` |
|
||||
| `variant-ghost-warning` | `preset-ghost-warning` |
|
||||
| `variant-ringed-warning` | `preset-outlined-warning` |
|
||||
| `variant-filled-primary` | `preset-filled-primary` |
|
||||
| `variant-filled-warning` | `preset-filled-warning` |
|
||||
| `rounded-container-token` | `rounded-xl` |
|
||||
| `rounded-token` | `rounded-md` |
|
||||
| `card` (standalone) | `card preset-tonal-surface` or surface div pattern |
|
||||
| `badge` (standalone) | `badge preset-tonal-surface` |
|
||||
| `chip` | `badge preset-tonal-*` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Transitions & Animation
|
||||
|
||||
All interactive state changes must be smoothed — no hard pops.
|
||||
|
||||
| Element | Classes |
|
||||
|---|---|
|
||||
| Table row | `transition-colors duration-200` on `<tr>` |
|
||||
| List item card | `transition-colors duration-200` on `<li>` |
|
||||
| Link hover | `transition-colors duration-200` on `<a>` |
|
||||
| Info chips | `transition-colors duration-200` on `<span>` |
|
||||
| QR code toggle (size) | `transition-all duration-500` on `<img>` |
|
||||
| Collapsible sections | Use Skeleton `Accordion` or CSS `transition-all` — don't add custom unless needed |
|
||||
| Buttons | Handled automatically by Skeleton preset classes |
|
||||
|
||||
---
|
||||
|
||||
## 6. Loading / Skeleton States
|
||||
|
||||
When `liveQuery` data is still resolving, show pulse placeholders instead of nothing:
|
||||
|
||||
```svelte
|
||||
<!-- Title placeholder -->
|
||||
<div class="h-7 w-2/3 bg-surface-200-800 animate-pulse rounded"></div>
|
||||
|
||||
<!-- Chip/pill placeholder -->
|
||||
<div class="h-5 w-1/2 bg-surface-200-800 animate-pulse rounded-full"></div>
|
||||
|
||||
<!-- Icon placeholder -->
|
||||
<div class="h-5 w-5 bg-surface-200-800 animate-pulse rounded-full"></div>
|
||||
```
|
||||
|
||||
Always wrap in `{#if $lq__obj}{...}{:else}...skeleton...{/if}` — **never** show real content structure before data exists.
|
||||
|
||||
---
|
||||
|
||||
## 7. Dark Mode Rules
|
||||
|
||||
- **Never write `dark:` overrides for background or text colors.** The Firefly theme handles both modes through CSS variables. Writing `dark:bg-gray-800` or `dark:text-gray-400` bypasses the theme and breaks if the user switches themes.
|
||||
- **Exception allowed:** `dark:text-primary-300` and `dark:text-tertiary-300` in info chips are intentional — they reference theme variables that gracefully degrade.
|
||||
- **Exception allowed:** `dark:border-surface-700` in fine-grained border work when `border-surface-200-800` isn't strong enough.
|
||||
|
||||
---
|
||||
|
||||
## 8. Accessibility (Section 508 / WCAG 2.1 AA)
|
||||
|
||||
| Requirement | Implementation |
|
||||
|---|---|
|
||||
| Decorative icons | `aria-hidden="true"` on all `<span class="fas ...">` |
|
||||
| Icon-only buttons | `aria-label="..."` or `title="..."` + visible context |
|
||||
| Async content regions | `role="status" aria-live="polite"` on loading/empty sections |
|
||||
| Focus indicators | `focus-visible:ring-2 focus-visible:ring-primary-500` on custom interactive elements |
|
||||
| Interactive dialogs | `aria-haspopup="dialog"` on trigger buttons |
|
||||
| Form inputs | Visible `<label>` linked via `for` / `id`, or explicit `aria-label` |
|
||||
| Color-only information | Always pair color coding with icon or text — never color alone |
|
||||
| Minimum touch target | 44×44px effective hit area for all tap targets |
|
||||
|
||||
---
|
||||
|
||||
## 9. Debug Code — Remove Before Committing
|
||||
|
||||
These patterns are breakpoint debuggers added during development. **Never commit them:**
|
||||
|
||||
```html
|
||||
<!-- Breakpoint border debugger — REMOVE before commit -->
|
||||
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
|
||||
```
|
||||
|
||||
Also flag on code review:
|
||||
- `console.log(...)` that isn't behind a `log_lvl` guard
|
||||
- `border-dashed border-y-transparent border-r-transparent` left on production components
|
||||
- `overflow-x-scroll` (should be `overflow-x-auto`)
|
||||
|
||||
---
|
||||
|
||||
## 10. QR Code Pattern — Critical Bug Prevention
|
||||
|
||||
The async QR generation code uses a boolean `true` as a loading placeholder:
|
||||
|
||||
```typescript
|
||||
$events_sess.pres_mgmt.session_qr_url[$lq__obj.id] = true; // ← loading
|
||||
// ... async ...
|
||||
$events_sess.pres_mgmt.session_qr_url[$lq__obj.id] = result; // ← URL string
|
||||
```
|
||||
|
||||
**Always gate display on `typeof ... === 'string'`**, not just truthy:
|
||||
|
||||
```svelte
|
||||
<!-- ✅ Correct -->
|
||||
{#if typeof $events_sess.pres_mgmt.session_qr_url?.[$lq__obj.id] === 'string'}
|
||||
|
||||
<!-- ❌ Wrong — renders broken <img src="true"> during load -->
|
||||
{#if $events_sess.pres_mgmt.session_qr_url[$lq__obj.id]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Tailwind Utility Usage Notes
|
||||
|
||||
- **`opacity-*` for muted text**: Use `opacity-60` (secondary) or `opacity-40` (tertiary/hint) instead of `text-gray-*`. This works in any theme and both modes.
|
||||
- **`/` opacity modifier**: `bg-primary-500/10` is preferred over separate `opacity-10` — it targets only the background.
|
||||
- **`text-sm leading-relaxed`**: Standard for body-level descriptive text in cards.
|
||||
- **`tracking-wide uppercase`**: Use for section label/eyebrow text with `opacity-40`.
|
||||
- **`whitespace-pre-wrap`**: Required for any `<pre>` or `<p>` displaying user-entered multi-line text (preserves breaks without horizontal overflow).
|
||||
@@ -1,7 +1,7 @@
|
||||
# Aether Journals UI Update (2026)
|
||||
|
||||
> **Status:** 🚧 Stuck on Phase 4 (Security/Encryption Blockers)
|
||||
> **Last Updated:** 2026-01-14
|
||||
> **Status:** 🚧 Phase 4 Active (Security/Encryption Blockers remain; Style pass complete)
|
||||
> **Last Updated:** 2026-03-06
|
||||
> **Primary Agent:** Frontend SvelteKit Agent
|
||||
|
||||
## 1. Project Overview
|
||||
@@ -73,6 +73,10 @@ This document outlines the modernization of the Journals module UI in the Svelte
|
||||
- [x] Extract decryption workflow to non-reactive helper.
|
||||
- [x] **Standardize Configuration Modals:** Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
|
||||
- [x] **RESOLVED:** Decryption workflow stability (Fixed via dependency isolation).
|
||||
- [x] **Style Standardization (2026-03-06):** Full Skeleton v4 `preset-*` class pass across all 17 journal components. See style token table in Lessons Learned below.
|
||||
- [x] **Dark mode fixes:** Entry content hover, journal view section/description background and text colors.
|
||||
- [x] **Modal close button:** All 3 config modals use `dismissable={false}` + explicit `<X>` button in header snippet for correct right-aligned placement.
|
||||
- [x] **Global select padding:** Added `padding-inline: 0.5rem` to `@layer base` in `app.css` (safe — utility `px-*` classes override it where intentional).
|
||||
- [ ] Solidify E2EE passcode system for Journals and Entries.
|
||||
- [ ] Audit encryption flow for Quick Added and Imported entries.
|
||||
- [ ] Integrate Outbound Email sharing.
|
||||
@@ -84,23 +88,58 @@ This document outlines the modernization of the Journals module UI in the Svelte
|
||||
During the implementation of the Privacy/Decryption toggle and the new Configuration Modals, we encountered critical browser hangs caused by infinite reactivity loops. Here is how we resolved them:
|
||||
|
||||
### 1. Rigorous Dependency Isolation (`untrack`)
|
||||
Svelte 5 runes (`$effect`, `$derived`) automatically track **every** reactive variable read inside them.
|
||||
Svelte 5 runes (`$effect`, `$derived`) automatically track **every** reactive variable read inside them.
|
||||
* **The Problem:** An effect would read `save_status` or `tmp_entry_obj.content` to decide if it should sync, but the act of syncing would update those same variables, re-triggering the effect.
|
||||
* **The Fix:** Wrap any "check-only" state or store reads in `untrack(() => ... )`. This allows the effect to use the value without becoming a dependency of it. This is **CRITICAL** when initializing local state from props inside an effect.
|
||||
|
||||
### 2. Standardized Modal UI ("Aether Orange")
|
||||
We have established a unified design language for configuration interfaces:
|
||||
* **Header/Footer:** Use `bg-orange-100 dark:bg-orange-900` with consistent borders.
|
||||
* **Tabs:** Center-aligned tabbed navigation using Skeleton UI `btn` and `variant-filled-primary` / `variant-soft-surface`.
|
||||
### 2. Standardized Modal UI ("Aether Orange") & Style Token Conventions
|
||||
We have established a unified design language for configuration interfaces and all Journals UI. **Use these as the module template.**
|
||||
|
||||
#### Modal Chrome
|
||||
* **Header/Footer:** `bg-orange-100 dark:bg-orange-900` with consistent orange borders.
|
||||
* **Close button:** Always use `dismissable={false}` on the `<Modal>` and add an explicit `<button>` with `<X>` inside the `{#snippet header()}` so placement is fully in our control. The `flex-1` class on the `<h3>` pushes it right.
|
||||
* **Tabs:** Center-aligned `btn btn-sm` with `preset-filled-primary` (active) / `preset-tonal-surface` (inactive).
|
||||
* **Icons:** Every tab and primary action should have a Lucide icon for better scannability.
|
||||
* **Explicit Persistence:** All configuration modals must follow an "Edit working copy -> Save Changes" pattern to prevent accidental store/API churn.
|
||||
* **Explicit Persistence:** Follow "Edit working copy → Save Changes" pattern to prevent accidental store/API churn.
|
||||
|
||||
#### Skeleton v4 Style Token Reference (Journals = canonical example)
|
||||
| Intent | Class |
|
||||
|---|---|
|
||||
| Primary CTA (save, create, open) | `btn preset-filled-primary` |
|
||||
| Neutral / cancel / close | `btn preset-tonal-surface` |
|
||||
| Secondary action | `btn preset-tonal-secondary` |
|
||||
| Success (confirmed save) | `btn preset-filled-success` |
|
||||
| Warning (caution action) | `btn preset-tonal-warning hover:preset-filled-warning-500` |
|
||||
| Error / danger (delete, force reset) | `btn preset-tonal-error hover:preset-filled-error-500` |
|
||||
| Active tab | `preset-filled-primary` |
|
||||
| Inactive tab | `preset-tonal-surface` |
|
||||
| Icon button | `btn-icon btn-icon-sm preset-tonal-surface` |
|
||||
| Input (base) | `input` |
|
||||
| Input (small) | `input input-sm` |
|
||||
| Select (base) | `select` |
|
||||
| Select (small) | `select select-sm` |
|
||||
| Textarea | `textarea` |
|
||||
| Badge (info/neutral) | `badge preset-tonal-surface` |
|
||||
| Badge (success) | `badge preset-tonal-success` |
|
||||
| Badge (error) | `badge preset-tonal-error` |
|
||||
|
||||
#### Removed Patterns (never use)
|
||||
- `variant-filled-*`, `variant-soft-*`, `variant-ghost-*`, `variant-ringed-*` — Skeleton v2, removed
|
||||
- `variant-form-material` — Skeleton v2, removed from all inputs/selects/textareas
|
||||
- `input-bordered` — non-standard, removed
|
||||
- DaisyUI `modal` / `modal-box` / `modal-action` wrapper divs inside Flowbite `<Modal>` — removed
|
||||
|
||||
#### Dark Mode Rules
|
||||
- Any `bg-{color}-100` dynamic background **must** have a `dark:bg-gray-800` (or similar) override — light shades are unreadable in dark mode.
|
||||
- Hover states on content areas need both light and dark variants: `hover:bg-blue-100 dark:hover:bg-blue-950`.
|
||||
- Text locked to `dark:text-gray-900` is almost always wrong — use `dark:text-gray-100`.
|
||||
|
||||
### 3. Dexie LiveQuery Subscriptions
|
||||
* **The Problem:** Accessing `liveQuery` observables directly in templates results in `[object Object]` or `undefined` property errors.
|
||||
* **The Mandate:** ALWAYS use the `$` prefix (e.g. `$lq__obj`) when passing or using data from a Dexie `liveQuery`.
|
||||
|
||||
### 4. Manual Deep Copying vs. Proxies
|
||||
Svelte 5 state is backed by Proxies.
|
||||
Svelte 5 state is backed by Proxies.
|
||||
* **The Problem:** Using `JSON.parse(JSON.stringify(proxy))` can sometimes trigger unexpected behavior or loops when used inside a reactive context.
|
||||
* **The Fix:** Implement a manual `deep_copy` helper or selective property assignment when syncing "Original" vs "Temporary" state. This ensures `orig_entry_obj` is a plain JS object, making the `has_unsaved_changes` check stable.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user