Renamed a directory to be more consistent
This commit is contained in:
289
documentation/archive/PROJECT__AE_Access_Control_UX.md
Normal file
289
documentation/archive/PROJECT__AE_Access_Control_UX.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# PROJECT: Access Control UX — Session Expired & Access Denied
|
||||
|
||||
**Status:** Complete
|
||||
**Priority:** Medium-High
|
||||
**Created:** 2026-02
|
||||
**Updated:** 2026-03-11
|
||||
**Related:** `src/routes/+layout.svelte`, `src/lib/ae_api/`, `src/lib/stores/ae_stores.ts`
|
||||
|
||||
---
|
||||
|
||||
## 1. Objective
|
||||
|
||||
Clean up inconsistent access-denied and session-expired UX across the app:
|
||||
|
||||
1. **Session Expired banner** — When the API returns 401/403, show a non-blocking dismissible banner in the root layout rather than silently failing. The `flag_expired` placeholder in the root layout is already wired for this but nothing sets it.
|
||||
2. **Standardize Access Denied display** — Replace the one-off browser `alert()` in event settings with a proper in-page gate. Create a small reusable component for inline denial cards.
|
||||
3. **Maintain intentional special cases** — IDAA Novi UUID gate and the Root Site Access Key gate are correct and must not be touched.
|
||||
|
||||
---
|
||||
|
||||
## 2. Current State Inventory
|
||||
|
||||
### Pattern A — Root Layout: Site Access Key Gate ✅ Working, keep as-is
|
||||
|
||||
**File:** `src/routes/+layout.svelte` lines 130–135, 299–305
|
||||
**Type:** Full-screen blocker
|
||||
**Logic:** If `site_access_key` is set and `allow_access` key doesn't match + user is not `trusted_access`, `flag_denied = true`.
|
||||
**Current UI:** Minimal full-screen `<h1>Access Denied</h1>` + Reload button.
|
||||
**Verdict:** Works correctly. The minimal styling is intentional (it's a hard site gate). Keep but no code change needed unless you want to polish the styling later.
|
||||
|
||||
---
|
||||
|
||||
### Pattern B — Root Layout: Session Expired Banner 🔴 Declared, never set
|
||||
|
||||
**File:** `src/routes/+layout.svelte` line 63
|
||||
**Variable:** `let flag_expired: boolean = $state(false)` — **never set anywhere.**
|
||||
**Intent:** This should show a non-blocking dismissible banner whenever the API returns 401/403, signaling a stale JWT/session.
|
||||
**Gap:** API helpers detect 401/403 and log diagnostic info but never fire any store event.
|
||||
**Fix:** See Implementation Plan step 1.
|
||||
|
||||
---
|
||||
|
||||
### Pattern C — Core Layout: Manager Access Gate ✅ Already correct
|
||||
|
||||
**File:** `src/routes/core/+layout.svelte` lines 13–20, 62–75
|
||||
**Logic:**
|
||||
- `onMount`: 500ms delay then `goto('/')` if still not `manager_access` (handles slow hydration)
|
||||
- `{:else}` block: Shows a styled "Access Restricted" card with Lock icon, description, "Return Home" button while waiting/denied
|
||||
|
||||
**Verdict:** This pattern is correct and consistent. The `{:else}` visual gate prevents flashing. The delayed redirect is a graceful fallback. **No changes needed.**
|
||||
|
||||
---
|
||||
|
||||
### Pattern D — IDAA Layout: Novi UUID Gate ✅ Intentionally custom, keep as-is
|
||||
|
||||
**File:** `src/routes/idaa/(idaa)/+layout.svelte`
|
||||
**Logic:** Async POST to `https://www.idaa.org/api` to verify Novi UUID. `novi_verifying` flag prevents Access Denied flash during network round-trip.
|
||||
**Verdict:** Intentionally custom to IDAA's member verification flow. **Do not standardize or touch this.**
|
||||
|
||||
---
|
||||
|
||||
### Pattern E — Event Settings: browser `alert()` 🟡 Needs fix
|
||||
|
||||
**File:** `src/routes/events/[event_id]/settings/+page.svelte` lines 44–47
|
||||
**Current code:**
|
||||
```ts
|
||||
if (!$ae_loc.administrator_access) {
|
||||
if (browser) {
|
||||
alert('Access Denied: Administrative privileges and Edit Mode required.');
|
||||
goto(`/events/${event_id}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
**Problems:**
|
||||
1. `alert()` is a blocking browser dialog — ugly, inconsistent with app UX
|
||||
2. Runs in module-level `if` block (not `onMount`) — can fire before hydration is fully complete
|
||||
3. No visual component shown; just redirects
|
||||
|
||||
**Fix:** Remove `alert()`, move check into `onMount` with a small delay (like `/core` pattern), or add an inline gate using a reusable component.
|
||||
|
||||
---
|
||||
|
||||
### Pattern F — Badge Review: Inline "Access Denied" Card 🟡 Acceptable, minor polish
|
||||
|
||||
**File:** `src/routes/events/[event_id]/(badges)/badges/[badge_id]/review/+page.svelte` lines 315–330
|
||||
**Context:** Passcode check failure — attendee entered wrong passcode
|
||||
**Current UI:**
|
||||
```html
|
||||
<div class="card p-6 space-y-4 max-w-sm">
|
||||
<div class="flex items-center gap-2 text-error-500">
|
||||
<h3 class="text-lg font-semibold">Access Denied</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-700">{passcode_error}</p>
|
||||
<button ... >Try Again</button>
|
||||
</div>
|
||||
```
|
||||
**Verdict:** Contextually appropriate and functional. The "Try Again" button is good UX. This is a prime candidate to be replaced with a reusable component once one exists, but it is not broken.
|
||||
|
||||
---
|
||||
|
||||
### Pattern G — API Helpers: 401/403 Detection Without UI Feedback 🔴 Gap
|
||||
|
||||
**Files:** `src/lib/ae_api/api_get_object.ts`, `api_post_object.ts`, `api_patch_object.ts` (all ~line 237)
|
||||
**Current behavior:** Logs auth diagnostics to console, returns `false` or `null`. No store event fired.
|
||||
**Gap:** When a JWT expires mid-session, the user sees requests silently fail (data doesn't load/save) with no explanation. They may think the app broke.
|
||||
**Fix:** On 401/403, set `ae_auth_error` store → root layout watches it and sets `flag_expired = true`.
|
||||
|
||||
---
|
||||
|
||||
### Pattern H — Presenter Auth (`auth__person`) — Existing system, no UX issues to fix now
|
||||
|
||||
**Store:** `$events_loc.auth__person` — stores authenticated presenter identity
|
||||
**URL params:** `?person_id=...&person_pass=...&presentation_id=...&presenter_id=...`
|
||||
**Anonymous toggle:** Per-event config allows presenters to upload files without signing in
|
||||
**Verdict:** Auth system is working. The gating UI in the presenter pages is contextually managed. Not in scope for this cleanup. Revisit when building out the Leads feature or a future auth refactor.
|
||||
|
||||
---
|
||||
|
||||
## 3. Issues Ranked by Priority
|
||||
|
||||
| # | Severity | Issue | File | Fix |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 🔴 High | API 401/403 silently fails — users have no feedback | `api_*.ts` | Wire to `ae_auth_error` store |
|
||||
| 2 | 🔴 High | `flag_expired` never set — session expired banner never shows | `+layout.svelte` | Watch `ae_auth_error`, render banner |
|
||||
| 3 | 🟡 Medium | `alert()` in event settings — ugly, blocking, not idiomatic | `settings/+page.svelte` | Replace with `onMount` gate + reusable component |
|
||||
| 4 | 🟢 Low | Badge review inline card — not reusing a component | `badges/.../review/+page.svelte` | Replace when `element_access_denied.svelte` is ready |
|
||||
|
||||
---
|
||||
|
||||
## 4. Design Decisions
|
||||
|
||||
### 4a. Session Expired Banner Design
|
||||
|
||||
- **Non-blocking top bar** — similar to the existing `is_offline` and `api_unreachable` banners in the root layout
|
||||
- **Dismissible** — user clicks X to clear; or auto-hides after signing back in
|
||||
- **Message:** "Your session has expired. Please reload or sign in again." with a Reload button
|
||||
- **Trigger:** Any 401 or 403 from any of the three API helpers
|
||||
|
||||
### 4b. `ae_auth_error` Store
|
||||
|
||||
Simple writable in `ae_stores.ts`:
|
||||
```ts
|
||||
export const ae_auth_error = writable<{ type: 'expired' | 'denied' | null, ts: number | null }>({ type: null, ts: null });
|
||||
```
|
||||
This is intentionally minimal — just enough to signal the root layout.
|
||||
|
||||
### 4c. Reusable `element_access_denied.svelte`
|
||||
|
||||
A small card component for inline access denial within a page:
|
||||
```
|
||||
Props:
|
||||
- title?: string (default: "Access Denied")
|
||||
- message?: string (default: "You do not have permission to view this content.")
|
||||
- show_reload?: boolean
|
||||
- show_return_home?: boolean
|
||||
- action_label?: string (optional extra button)
|
||||
- on_action?: () => void
|
||||
```
|
||||
Location: `src/lib/elements/element_access_denied.svelte`
|
||||
|
||||
### 4d. Event Settings Fix
|
||||
|
||||
The settings page check should mirror the `/core` pattern:
|
||||
- Move to `onMount` with 500ms grace delay
|
||||
- No `alert()` — if not authorized, the redirect fires silently after the delay
|
||||
- Add inline gate (`{:else}` block with "Access Restricted" message) if the user somehow lands here
|
||||
|
||||
### 4e. What We Are NOT Changing
|
||||
|
||||
- Root Layout site access key gate — working correctly
|
||||
- `/core` layout — already correct
|
||||
- IDAA Novi UUID gate — intentionally custom
|
||||
- Presenter auth system (`auth__person`) — not in scope
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Plan
|
||||
|
||||
### Step 1: Add `ae_auth_error` store ✅ DONE (2026-03-11)
|
||||
|
||||
**File:** `src/lib/stores/ae_stores.ts`
|
||||
|
||||
Add after the existing store declarations:
|
||||
```ts
|
||||
// Auth error signal — set by API helpers on 401/403 to trigger root layout session-expired banner
|
||||
export const ae_auth_error = writable<{ type: 'expired' | null, ts: number | null }>({ type: null, ts: null });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Wire API helpers to `ae_auth_error` ✅ DONE (2026-03-11)
|
||||
|
||||
**Files:** `src/lib/ae_api/api_get_object.ts`, `api_post_object.ts`, `api_patch_object.ts` (same pattern in all three)
|
||||
|
||||
In the existing `if (response.status === 401 || response.status === 403)` block, add one line after the existing `console.warn(...)`:
|
||||
```ts
|
||||
import { ae_auth_error } from '$lib/stores/ae_stores';
|
||||
// ...
|
||||
ae_auth_error.set({ type: 'expired', ts: Date.now() });
|
||||
```
|
||||
|
||||
**Note:** Only import `ae_auth_error` — no other store changes. Do NOT import `ae_auth_error` at module level if the API helpers are used SSR-side. Use a dynamic import or guard with `browser` check if needed.
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Wire `flag_expired` in root layout ✅ DONE (2026-03-11)
|
||||
|
||||
**File:** `src/routes/+layout.svelte`
|
||||
|
||||
Add an `$effect` that watches `$ae_auth_error` and sets `flag_expired`:
|
||||
```ts
|
||||
$effect(() => {
|
||||
if ($ae_auth_error?.type === 'expired' && $ae_auth_error?.ts) {
|
||||
untrack(() => { flag_expired = true; });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Add the dismissible banner to the template (after/near the existing `is_offline` banner, in the `{#if browser && $ae_loc?.allow_access}` block):
|
||||
```html
|
||||
{#if flag_expired}
|
||||
<div class="fixed top-0 left-0 right-0 z-50 bg-warning-500 text-white px-4 py-2 flex items-center justify-between">
|
||||
<p class="text-sm font-semibold">Your session has expired. Please reload or sign in again.</p>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-sm preset-filled-surface" onclick={() => window.location.reload()}>Reload</button>
|
||||
<button class="btn btn-sm" onclick={() => { flag_expired = false; ae_auth_error.set({ type: null, ts: null }); }}>Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Create `element_access_denied.svelte` ✅ DONE (2026-03-11)
|
||||
|
||||
**File:** `src/lib/elements/element_access_denied.svelte`
|
||||
|
||||
Reusable card for inline access denial. Props per design decision 4c.
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Fix Event Settings `alert()` ✅ DONE (2026-03-11)
|
||||
|
||||
**File:** `src/routes/events/[event_id]/settings/+page.svelte`
|
||||
|
||||
Replace the module-level `if (!$ae_loc.administrator_access)` + `alert()` block with:
|
||||
1. Move check into `onMount` with the same 500ms grace-delay pattern as `/core`
|
||||
2. Add `{:else}` gate in the template using `element_access_denied.svelte`
|
||||
3. Remove the `browser` guard (not needed inside `onMount`)
|
||||
|
||||
---
|
||||
|
||||
### Step 6 (Optional / Low Priority): Swap badge review inline card ✅ DONE (2026-03-11)
|
||||
|
||||
**File:** `src/routes/events/[event_id]/(badges)/badges/[badge_id]/review/+page.svelte`
|
||||
|
||||
Replace inline access denied card with `element_access_denied.svelte` once the component exists. Keep "Try Again" action via `on_action` prop.
|
||||
|
||||
---
|
||||
|
||||
## 6. Files to Modify Summary
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `src/lib/stores/ae_stores.ts` | Add `ae_auth_error` writable store |
|
||||
| `src/lib/ae_api/api_get_object.ts` | Set `ae_auth_error` on 401/403 |
|
||||
| `src/lib/ae_api/api_post_object.ts` | Set `ae_auth_error` on 401/403 |
|
||||
| `src/lib/ae_api/api_patch_object.ts` | Set `ae_auth_error` on 401/403 |
|
||||
| `src/routes/+layout.svelte` | Watch `ae_auth_error`, render session-expired banner |
|
||||
| `src/routes/events/[event_id]/settings/+page.svelte` | Remove `alert()`, fix auth gate pattern |
|
||||
| `src/lib/elements/element_access_denied.svelte` | **NEW** — reusable inline denial card |
|
||||
| `src/routes/events/[event_id]/(badges)/badges/[badge_id]/review/+page.svelte` | Swap inline card with component (low priority) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Testing Notes
|
||||
|
||||
- **Session expired banner:** Force a 401 by testing with an expired JWT or by calling an API with wrong credentials. Banner should appear. Dismiss should clear it. Reload should reload browser.
|
||||
- **Event settings gate:** Navigate to `/events/{id}/settings` without `administrator_access`. Should redirect without any `alert()` dialog.
|
||||
- **Badge review:** Enter a bad passcode — Access Denied card should appear with "Try Again".
|
||||
- **IDAA, /core, root site key gate:** Verify no regressions.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risks & Notes
|
||||
|
||||
- The API helpers (`api_get_object.ts` etc.) run in a module context that is imported across many components. The `ae_auth_error` store must only be imported/used inside a `browser` guard or dynamically if the helpers are ever called SSR-side. Check `src/lib/ae_core/ae_core__site.ts` (which uses these helpers during SSR hydration) — **do not set the store from SSR context.** Add `if (browser)` before the `ae_auth_error.set(...)` call.
|
||||
- The root layout sync effect runs `untrack()` to prevent circular store updates. The new `$effect` for `ae_auth_error` must also use `untrack()` to be safe.
|
||||
- `flag_expired` should NOT permanently gate the UI — it should only show a top banner. The user may have been mid-editing and should not lose their work. The current `flag_denied` full-screen block is for site key access only.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Project: AE Docker + CI BuildKit Implementation
|
||||
|
||||
**Status:** Proposed
|
||||
|
||||
**Goal:** Make Docker image builds for Aether cache-friendly using BuildKit/buildx and CI registry caching, while keeping local developer caches small and manageable.
|
||||
|
||||
Summary
|
||||
- Implement a BuildKit-friendly multi-stage `Dockerfile` pattern for frontend and API images.
|
||||
- Add CI `buildx` examples that push/read registry-based cache to avoid local disk bloat.
|
||||
- Provide cache retention/rotation guidance and developer commands for safe pruning.
|
||||
|
||||
Scope
|
||||
- Repository areas: `aether_container_env/`, root `Dockerfile` (if present), and CI pipeline definitions (Gitea/Drone or other).
|
||||
- Non-goal: full CI pipeline migration to a new provider. This work provides CI snippets and a PR-ready set of files for your CI team.
|
||||
|
||||
Deliverables (this PR)
|
||||
- `documentation/PROJECT__AE_Docker_CI_BuildKit_implement.md` (this file)
|
||||
- `aether_container_env/Dockerfile.buildkit.example` — BuildKit-friendly multi-stage Dockerfile example.
|
||||
- `aether_container_env/ci_buildx_example.sh` — standalone CI script examples (registry cache + local cache usage).
|
||||
- `documentation/AE_Docker_CI_cache_policy.md` — cache rotation and prune guidance.
|
||||
|
||||
Tasks (implementation checklist)
|
||||
- [ ] Review existing `Dockerfile`(s) under `aether_container_env/` and repository root.
|
||||
- [ ] Replace/extend Dockerfile with multi-stage BuildKit-friendly layout (use example as guide).
|
||||
- [ ] Ensure `.dockerignore` (already added) excludes large build artifacts.
|
||||
- [ ] Add CI step using `docker buildx build` with `--cache-from` and `--cache-to` pointed at a registry cache.
|
||||
- [ ] Add a scheduled job or registry lifecycle rule to delete old cache images (30 days default).
|
||||
- [ ] Document required CI secrets and permissions (registry write/read) for the operations team.
|
||||
- [ ] Run verification builds (dev local with BuildKit; CI runs with cache) and record timings.
|
||||
|
||||
Verification
|
||||
- Local dev: `DOCKER_BUILDKIT=1` build with `--cache-to`/`--cache-from` shows cache hits on second run and faster build time.
|
||||
- CI: subsequent CI runs log `cache hit` from `buildx` and total build time reduced vs baseline.
|
||||
- Confirm registry contains `cache` image tags and that rotation job/prune removes old entries.
|
||||
|
||||
Notes about Gitea/CI
|
||||
- Gitea does not include native Actions like GitHub; teams typically use Drone CI, Tekton, or a self-hosted runner that can execute the `docker`/`buildx` CLI.
|
||||
- The provided `ci_buildx_example.sh` is intentionally provider-agnostic — pasteable into Drone, Jenkins, GitLab CI, or any shell-capable runner.
|
||||
|
||||
Risks & Mitigations
|
||||
- Risk: Unbounded registry cache growth. Mitigation: enforce retention policy and rotation job; prefer a single `cache` tag reused by CI.
|
||||
- Risk: Developers unfamiliar with BuildKit. Mitigation: examples show simple `DOCKER_BUILDKIT=1` usage and local cache prune commands.
|
||||
|
||||
Next steps for the container team
|
||||
1. Review examples in `aether_container_env/` and adapt the Dockerfile to your runtime constraints (ssl certs, env injection, secrets).
|
||||
2. Add a CI job using the `ci_buildx_example.sh` snippet; configure registry credentials as secrets.
|
||||
3. Add a scheduled job to rotate/delete old cache images or configure registry lifecycle rules.
|
||||
4. Run a before/after benchmark of `time npm run build:prod` inside the build stage to quantify improvement.
|
||||
|
||||
Files included in this PR for reference:
|
||||
- `aether_container_env/Dockerfile.buildkit.example`
|
||||
- `aether_container_env/ci_buildx_example.sh`
|
||||
- `documentation/AE_Docker_CI_cache_policy.md`
|
||||
175
documentation/archive/PROJECT__AE_Events_Exhibitor_Leads_v3.md
Normal file
175
documentation/archive/PROJECT__AE_Events_Exhibitor_Leads_v3.md
Normal file
@@ -0,0 +1,175 @@
|
||||
Aether Events Exhibitor Leads v3
|
||||
=======
|
||||
|
||||
|
||||
## Overview:
|
||||
* Mobile first!
|
||||
* Offline caching for spotty network connections
|
||||
* Clean and simple
|
||||
* Exhibitors have between 0 and X license
|
||||
* Sign in and "claim" an assigned license linked to an Exhibitor
|
||||
* Collect and manage Leads per Exhibitor
|
||||
* Export Leads data to CSV or XLSX
|
||||
* Exhibitors will have the link to their Exhibit sent to them or they can use the Exhibit Search to find their Exhibit and sign in with the shared passcode or their assigned licensed user credentials.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Primary Leads Pages/Tabs
|
||||
1. start - Sign In / Licenses
|
||||
* Exhibit passcode sign in
|
||||
* Exhibitor Leads user sign in
|
||||
* Payment - Leads Payment
|
||||
2. add - Add (Search/QR)
|
||||
* Text search
|
||||
* QR scan
|
||||
3. leads - Leads List
|
||||
* List of Attendees (Event Badge ID) linked to Exhibit
|
||||
* Click to view/edit Exhibit Tracking entry
|
||||
4. manage - Leads (app and exhibit) Manage
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Exhibitor Sign In and Licenses:
|
||||
* An Exhibit needs to have a "Shared exhibit passcode" for primary sign in.
|
||||
* A Licensed Exhibit staff person needs to have a License assigned to them. It is important that the email address not be changed per license. Or if they do, be aware of the affects on tracked attendees. They can then sign in with that email address and passcode assigned. And/Or we need to make the email sign in link work as well.
|
||||
* Once signed in with the Exhibit passcode, they can change it (passcode) and assign Exhibit Leads licenses once they have paid.
|
||||
* An exhibitor marked as "priority" means they have paid. Only OSIT admins can mark them as paid.
|
||||
* Should at least claim/assign one initial user license so an Exhibit related staff can sign in (without the shared Exhibit passcode). Then add, update, or remove licenses based on max allowed per Exhitibor.
|
||||
|
||||
Payment:
|
||||
* Not Paid: <span class="fas fa-question text-red-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Waiting for payment
|
||||
* Paid: <span class="fas fa-check text-green-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Marked as paid
|
||||
|
||||
### Licenses:
|
||||
A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode can add/edit/remove icenses.
|
||||
|
||||
License:
|
||||
* full_name
|
||||
* email
|
||||
* passcode
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Structure Overview
|
||||
* There are 4 primary tabs for the Event Exhibit Leads module.
|
||||
* The overall header/footer should hide by default once signed in.
|
||||
* If not signed in, only the first tab, to start and sign in is shown.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [tab 1] Start / Sign In / Payment
|
||||
* Only shows when not signed in as Exhibit Licensed Leads User
|
||||
* Sign in with Exhibit passcode
|
||||
* This will then allow them to Manage the Exhibit License
|
||||
* Sign in with Exhibit Licensed user
|
||||
|
||||
### [tab section] Payment
|
||||
* Shows if signed in with Exhibit Passcode and not marked as paid / priority
|
||||
* Field for license info - event_exhibit.license_li_json
|
||||
* Use Exhibit passcode first?
|
||||
* Select number of licenses
|
||||
* Select number of small/large devices
|
||||
* Make payment (Stripe)
|
||||
|
||||
### [tab section] Licensed Users
|
||||
* Shows if signed in with Exhibit Passcode and are marked as paid / priority
|
||||
* Fill in Exhibit staff users per max licenses
|
||||
|
||||
|
||||
## [tab 2] Add - Search / QR Scan
|
||||
* One button that toggles between showing and hiding the QR add mode? The text search is always shown above or below the QR scan camera image area.
|
||||
* Search - Essentially the same as the Badge search. Full name, email, affiliations, ID, etc
|
||||
* QR Scan - Allow for auto add toggle instead of confirming per scan. Allow for manual entire of Badge ID
|
||||
* The QR scan is basically just using the Event Badge ID encoded in the persons QR code on their badge. In fact it can probably just populate the text search field?
|
||||
* Must include the Leads licensed user's email address when adding to the Exhibitor Tracking list. Linked using event_exhibit_tracking with the Licensed user's email address.
|
||||
|
||||
|
||||
## [tab 3] Leads - List of Attendee Leads for Exhibitor
|
||||
* Allow for toggle between showing all per Exhibit and per licensed user based on their email address. Not perfect, but works well enough.
|
||||
* Allow for easy edit or remove
|
||||
* Button to Export Data - CSV or XLSX
|
||||
|
||||
* Toggle for show/hide Hidden records
|
||||
* Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first
|
||||
|
||||
|
||||
## [tab 4] Manage / Config
|
||||
### Exhibit Specific
|
||||
* Priority/payment toggle - Administrator Access or above
|
||||
* Max licenses (number) - readonly or edit for Administrator Access or above
|
||||
* Small devices (number) - readonly or edit for Administrator Access or above
|
||||
* Large devices (number) - readonly or edit for Administrator Access or above
|
||||
* Exhibit (shared) Passcode
|
||||
* Same Exhibit Leads License list component as the Start tab's Licensed Users section
|
||||
|
||||
### App Specific
|
||||
|
||||
* Show/Hide Payment Tab
|
||||
* Additional Settings:
|
||||
* List refresh interval in seconds - default 25 seconds; 1 second to 2 minutes (120000)
|
||||
* Basic reload/refresh
|
||||
* Clear Indexed DB
|
||||
* Clear localStorage
|
||||
* Auto hide header/footer on sign in - default true
|
||||
* (?) Turn on iframe mode
|
||||
* (?) Show or hide additional details - Use "$events_loc.show_details"?
|
||||
|
||||
|
||||
## [page] View / Edit Tracked Attendee (event_exhbit_id, event_badge_id
|
||||
* Able to edit the obvious things
|
||||
* Can add custom exhibitor_notes and custom responses_json
|
||||
* Can set Priority (Star/Flag)
|
||||
* Can set Sort (number)
|
||||
* Can set Hide (toggle)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## App Specific - Leads Module Config Options and Stored Data
|
||||
* Signed in using Shared Exhibit Passcode
|
||||
* Can make payment and manage user licenses
|
||||
* Need to be able to sign in with Shared Passcode and Leads License passcode.
|
||||
* Signed in using one Leads License slot they were assigned to
|
||||
* Things will be tied to their email address
|
||||
* Can add, edit, remove, etc the Leads specific to an Exhibit.
|
||||
* This list is shared among all Licensed staff for a specific Exhibit. It is recommended they stay toggled to only showing their additions by default.
|
||||
* Need to be able to sign in with Shared Passcode and Leads License passcode.
|
||||
* Show and Hide payment tab (override sort of)
|
||||
* Show and Hide header/footer (override sort of)
|
||||
* Use iframe mode??
|
||||
* Light and Dark mode??
|
||||
* List refresh interval
|
||||
* Tracking list sorting
|
||||
* Show and hide hidden records
|
||||
* Show and hide enabled records (Administrator Access or above)
|
||||
|
||||
There are essentially 3 ways to "Sign In":
|
||||
1. Aether Platform in general using a defined Core User or using a Client Site specific passcode.
|
||||
2. Exhibit shared passcode for general Exhibitor management. Should only be briefly needed to pay and assign license slots
|
||||
3. Licensed Leads User using the email address and passcode assigned by the Exhibitor manager or OSIT/client staff person.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## First Steps for Leads v3
|
||||
Create stub directories, pages, and other supporting files for each primary tab, and each primary section within each tab, and for viewing a specific Lead (Exhibit Tracking).
|
||||
|
||||
Once we have that looking good, then we can add the functionality. So we are kind of laying out the framework and pre-documenting (commenting) the files as we go.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## For Reference if Needed
|
||||
Known goodish reference version of Leads v2ish:
|
||||
d1021e28227db6d49b16cc48e324872b1a322da3 November 19, 2025 at 10:45 PM
|
||||
Hopefully that is not needed...
|
||||
@@ -0,0 +1,153 @@
|
||||
Aether Events Exhibitor Leads v3 - Details
|
||||
=======
|
||||
|
||||
## [root page] Leads for Exhibitors Main Landing
|
||||
* Has a search for Exhibits section that allows for searching Exhibits by name or code.
|
||||
* Results can be sortable. They can be sorted by name, booth number, or last created/updated.
|
||||
* This is for exhibitors to find their Exhibit and sign in with the shared passcode or their assigned licensed user credentials.
|
||||
* If not signed in with Administrator Access or above, then only show results for Exhibitors that have been marked as "priority" (paid). Otherwise show all Exhibitors (to admins).
|
||||
* Has a list of Exhibits with basic info and link to Exhibit Leads management page for each Exhibit. Do not show results by default or if less than 3 characters in search and they are not signed in with Trusted Access or above.
|
||||
|
||||
|
||||
## [exhibit_id page] Exhibit Leads Module
|
||||
* This is minimalistic. Only the tabs and content needed.
|
||||
|
||||
### Header/Footer
|
||||
* Overall header/footer should hide by default once signed in. Use Aether iframe mode?
|
||||
* Leads module header should show the Exhibit name and booth number. It should also have a button to toggle the Add Lead tab and Lead List tab. There should also be a button to show the Manage/Config tab for the Exhibit.
|
||||
Header: [Name and Booth # text] [Add Lead / Lead List toggle] [Manage/Config button]
|
||||
|
||||
|
||||
### Tabs:
|
||||
I am probably using the term "tab" loosely here. It may just be sections that show/hide based on buttons or whatever. The main thing is to keep the UI simple and not overwhelm the user with too many options at once. The main flow should be to easily add leads and then view/manage those leads. The Manage/Config tab is more for the person managing the Exhibit and should be separate from the lead capture and management interface.
|
||||
|
||||
1. Start - Sign In / Licenses / Payment
|
||||
2. Add - Search/QR
|
||||
3. Leads - List of Attendee Leads for Exhibitor
|
||||
4. Manage - Leads (app and exhibit) Manage
|
||||
|
||||
|
||||
### [tab 1] Start / Sign In / Payment
|
||||
* Only shows when not signed in as Exhibit Licensed Leads User
|
||||
* Show notification and or message that this is a PWA and can be installed on their device for easier access.
|
||||
|
||||
* Sign in with Exhibit passcode
|
||||
* This will then allow them to Manage the Exhibit License
|
||||
* Sign in with Exhibit Licensed user
|
||||
* This will then allow them to Manage the Exhibit License
|
||||
* Payment - Leads Payment
|
||||
* Not Paid: <span class="fas fa-question text-red-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Waiting for payment
|
||||
* Paid: <span class="fas fa-check text-green-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Marked as paid
|
||||
* Need to switch to Lucide icons
|
||||
* Sections:
|
||||
* Sign in with Exhibit "shared" passcode
|
||||
* This will then allow them to Manage the Exhibit License
|
||||
* Change to Sign Out once signed in
|
||||
* Once signed in, show message to the Exhibit person. Allow them to select number of Licenses, make a payment, and manage the Licenses if paid.
|
||||
* Sign in with Exhibit Licensed user
|
||||
* This will then allow them to Manage the Exhibit License
|
||||
* Change to Sign Out once signed in
|
||||
* Once signed in, show message to the Leads user. A big button should allow them to then start adding leads.
|
||||
* Payment (Stripe) - Leads Payment
|
||||
* Only show if signed in with Exhibit "shared" passcode and not marked as paid.
|
||||
* Licenses (table):
|
||||
* A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode can add/edit/remove licenses.
|
||||
* License:
|
||||
* full_name
|
||||
* email
|
||||
* passcode
|
||||
* Buttons and Inputs:
|
||||
* Sign in with Exhibit passcode
|
||||
* Sign in with Exhibit Licensed user
|
||||
* Payment - Leads Payment
|
||||
* Add/Edit/Remove Licenses (if signed in with Exhibit passcode or Trusted Access or above)
|
||||
|
||||
### [tab 2] Add - Search/QR
|
||||
* Show only when signed in as Exhibit Licensed Leads User or Trusted Access or above.
|
||||
* Allow for text search of Attendee Badge ID, QR code, name, email, or affiliations.
|
||||
* Allow for QR code scan to add Attendee Badge as Lead.
|
||||
* Once found, show basic Attendee Badge info and button to "Add as Lead".
|
||||
* If already added as Lead, show message and button to "View Lead".
|
||||
* Sections:
|
||||
* Text search
|
||||
* QR scan
|
||||
* Results with "Add as Lead" or "View Lead" button
|
||||
* Buttons and Inputs:
|
||||
* Text input for search
|
||||
* Button to trigger search
|
||||
* Button to trigger QR scan (opens camera and scans QR code on badge)
|
||||
* Button to "Add as Lead" if Attendee Badge found and not already a Lead
|
||||
* Button to "View Lead" if Attendee Badge found and already a Lead
|
||||
Functions needed:
|
||||
* Search function to find Attendee Badge by Badge ID, QR code, name, email, or affiliations.
|
||||
* QR code scan function to read QR code and find Attendee Badge.
|
||||
* Add Lead function to create Exhibit_tracking entry linking Exhibit and Attendee Badge.
|
||||
|
||||
### [tab 3] Leads - List of Attendee Leads for Exhibitor
|
||||
* Allow for toggle between showing all per Exhibit and per licensed user based on their email address. Not perfect, but works well enough.
|
||||
* Allow for easy edit or remove
|
||||
* Sections:
|
||||
* List of Leads with basic info and buttons to Edit or Remove
|
||||
* Options:
|
||||
* Filter by Licensed user email address (dropdown of emails that have added leads for this Exhibit)
|
||||
* Toggle for show/hide Hidden records
|
||||
* Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first
|
||||
* Buttons and Inputs:
|
||||
* Button to Export Data - CSV or XLSX
|
||||
* Toggle for show/hide Hidden records
|
||||
* Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first
|
||||
* Should it have a text search?
|
||||
* NOTE: It is probably easiest for them to us the search tab to find a lead that has already been added. It will show "View Lead" button if already added.
|
||||
Functions needed:
|
||||
* Load Leads function to get Exhibit_tracking entries for the Exhibit.
|
||||
* Filter function to filter by Licensed user email address.
|
||||
* Sort function to sort by selected option.
|
||||
* Export function to export displayed Leads to CSV or XLSX.
|
||||
|
||||
### [tab 4] Manage - Leads (app and exhibit) Manage / Config
|
||||
#### Exhibit Specific
|
||||
* Priority/payment toggle - Administrator Access or above
|
||||
* Max licenses (number) - readonly or edit for Administrator Access or above
|
||||
* Small devices (number) - readonly or edit for Administrator Access or above
|
||||
* Large devices (number) - readonly or edit for Administrator Access or above
|
||||
* Exhibit (shared) Passcode
|
||||
* Same Exhibit Leads License list component as the Start tab's Licensed Users section
|
||||
|
||||
#### App Specific
|
||||
|
||||
* Show/Hide Payment Tab
|
||||
* Additional Settings:
|
||||
* List refresh interval in seconds - default 25 seconds; 1 second to 2 minutes (120000)
|
||||
* Basic reload/refresh
|
||||
* Clear Indexed DB
|
||||
* Clear localStorage
|
||||
* Auto hide header/footer on sign in - default true
|
||||
* (?) Turn on iframe mode
|
||||
* (?) Show or hide additional details - Use "$events_loc.show_details"?
|
||||
|
||||
* Sections:
|
||||
* Exhibit Specific Manage/Config
|
||||
* App Specific Manage/Config
|
||||
* Buttons and Inputs:
|
||||
* Exhibit Specific:
|
||||
* Priority/payment toggle - Administrator Access or above
|
||||
* Max licenses (number) - readonly or edit for Administrator Access or above
|
||||
* Small devices (number) - readonly or edit for Administrator Access or above
|
||||
* Large devices (number) - readonly or edit for Administrator Access or above
|
||||
* Exhibit (shared) Passcode
|
||||
* Same Exhibit Leads License list component as the Start tab's Licensed Users section
|
||||
* App Specific:
|
||||
* Show/Hide Payment Tab
|
||||
* Show last refresh time and counter for next refresh based on the List refresh interval setting.
|
||||
* Additional Settings:
|
||||
* List refresh interval in seconds - default 25 seconds; 1 second to 2 minutes (120000)
|
||||
* Basic reload/refresh (F5)
|
||||
* Clear Indexed DB
|
||||
* Clear localStorage
|
||||
* Auto hide header/footer on sign in - default true
|
||||
* (?) Turn on iframe mode
|
||||
* (?) Show or hide additional details - Use "$events_loc.show_details"?
|
||||
* Functions:
|
||||
* Update Exhibit configuration function to update the Exhibit with the new settings.
|
||||
* Update App configuration function to update the app-wide settings for the Leads module.
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
# PROJECT: Zebra ZC10L Hardware Test Day
|
||||
|
||||
**Created:** 2026-03-12
|
||||
**Planned date:** ~week of 2026-03-16 (printer rented for one day)
|
||||
**Hardware:** Zebra ZC10L, 3.5" × 5.5" PVC card stock
|
||||
**Goal:** Validate real-world badge printing before Axonius (NYC, mid-April 2026)
|
||||
**Owner:** Scott Idem / One Sky IT
|
||||
|
||||
---
|
||||
|
||||
## Before the Printer Arrives — Pre-Test Checklist
|
||||
|
||||
These must be done before the printer is on-site so you're not burning rental time on setup.
|
||||
|
||||
- [ ] **Remove debug outlines** from `print/+page.svelte` print CSS.
|
||||
The lime/blue/red/orange/purple/cyan debug outlines are still in the file. Remove the
|
||||
entire `TEMPORARY DEBUG OUTLINES` block. Commit before the test day.
|
||||
|
||||
- [ ] **Zebra ZC10L Linux driver** — install CUPS driver ahead of time.
|
||||
- Check Zebra's site for the Linux CUPS driver for ZC10L.
|
||||
- Install, configure in CUPS (`http://localhost:631`), do a test page print with a spare card.
|
||||
- Confirm the card feeds without jam and ink/dye-sub layer applies cleanly.
|
||||
- Driver may need the printer set to the correct card stock size (3.5" × 5.5").
|
||||
|
||||
- [ ] **Wire `style_href`** — add `<link rel="stylesheet" href={...}>` to `<svelte:head>` in
|
||||
`print/+page.svelte` when `$lq__event_badge_template_obj?.style_href` is set.
|
||||
Without this, any client-specific external CSS won't load.
|
||||
See `documentation/MODULE__AE_Events_Badge_Templates.md` → "External CSS Approach".
|
||||
|
||||
- [ ] **Confirm single-sided print (duplex=0)** — `duplex` backend field doesn't exist yet.
|
||||
For PVC cards, the badge back must NOT print. Verify this works by checking that
|
||||
`.badge_back` is hidden in `@media print` when the layout is `badge_3.5x5.5_pvc`.
|
||||
The PVC CSS (`badge_layout_zebra_zc10l_pvc.css`) may already handle this — confirm.
|
||||
If not, add a print rule: `[data-layout="badge_3.5x5.5_pvc"] .badge_back { display: none !important; }`
|
||||
|
||||
- [ ] **Test event + template in dev DB** — create/confirm:
|
||||
- Event with `mod_badges_json` configured
|
||||
- PVC template: `layout: badge_3.5x5.5_pvc`, `duplex: 0` (once backend supports it),
|
||||
`header_path` set to a real image URL, `badge_type_list` JSON populated
|
||||
- Test badge records (see "Test Data Set" below)
|
||||
|
||||
- [ ] **Test data set** — create badge records covering:
|
||||
- Short name: "Kim Lee"
|
||||
- Long name: "Bartholomew Vandenberghe-Christopoulos"
|
||||
- HTML in name: `<b>Dr.</b> Patricia Adams`
|
||||
- HTML in affiliations: `University of Minnesota<br>Dept. of Surgery`
|
||||
- Badge with no affiliations, no location
|
||||
- Badge with all 8 ticket codes set
|
||||
- Three different badge_type_codes (e.g. member, staff, guest) — to verify footer
|
||||
stripe color for each
|
||||
|
||||
- [ ] **Browser setup on kiosk machine** — confirm Chrome and Firefox both installed.
|
||||
Test print dialog settings once driver is working:
|
||||
- Chrome: Margins → None (required), paper size set to 3.5×5.5 if available
|
||||
- Firefox: should just work — `@page { size: 3.5in 5.5in }` is honored
|
||||
|
||||
---
|
||||
|
||||
## Test Day Checklist
|
||||
|
||||
### 1. Basic Print Path
|
||||
|
||||
- [ ] Card feeds and prints without jam
|
||||
- [ ] Badge fills the card edge-to-edge (no white border, no clipping)
|
||||
- [ ] Content horizontally centered on the card
|
||||
- [ ] Content vertically centered on the card
|
||||
- [ ] No debug outlines visible (confirm cleanup commit applied)
|
||||
|
||||
**Chrome:**
|
||||
- [ ] Print → Margins: **None** → correct output
|
||||
- [ ] Print → Margins: **Default** → bad (expected — documents the known issue)
|
||||
- [ ] Print → Margins: **Minimum** → correct output
|
||||
- [ ] "More settings" → paper size: does selecting 3.5×5.5 matter, or does Zebra driver override?
|
||||
|
||||
**Firefox:**
|
||||
- [ ] Print → just works out of the box
|
||||
|
||||
### 2. Single-Sided PVC
|
||||
|
||||
- [ ] Only the front face prints — back does NOT print
|
||||
- [ ] No second card ejected or blank card printed for the back
|
||||
|
||||
### 3. Visual Quality
|
||||
|
||||
- [ ] Font sizes readable at 3.5×5.5 physical scale (name, title, affiliations, location)
|
||||
- [ ] Auto-scaling text (v2) — does it look natural, not crunched?
|
||||
- [ ] Header image renders correctly (colors, resolution, no pixelation)
|
||||
- [ ] Footer stripe color correct for each badge_type_code tested
|
||||
- [ ] HTML in name/affiliations renders correctly (bold, line break, etc.)
|
||||
- [ ] Badge with no affiliations — no awkward blank space
|
||||
|
||||
### 4. Edge Cases — Badge Content
|
||||
|
||||
- [ ] Long name auto-scales without overflow or clipping
|
||||
- [ ] HTML markup in name field: `<b>Dr.</b>` renders bold on the physical card
|
||||
- [ ] HTML line break in affiliations: two-line org renders cleanly
|
||||
- [ ] Badge with no location — layout doesn't break
|
||||
- [ ] Badge with all 8 ticket/option codes — back of badge (if applicable) lays out cleanly
|
||||
|
||||
### 5. Print Tracking
|
||||
|
||||
- [ ] `print_count` increments after printing via the **Print Badge** button
|
||||
- [ ] `print_first_datetime` set on first print
|
||||
- [ ] "Printed N×" amber chip appears in print page header after first print
|
||||
- [ ] Reprint via Re-print shortcut (trusted + edit mode) does NOT increment count
|
||||
- [ ] Second print via Print Badge button DOES increment count (to 2)
|
||||
|
||||
### 6. QR Code
|
||||
|
||||
- [ ] QR on printed card is scannable with a phone camera
|
||||
- [ ] QR scans to the correct badge ID (test with `/events/[id]/badges` search by QR scan)
|
||||
- [ ] QR on back (if `show_qr_back=1`) also scans correctly
|
||||
- [ ] If `show_qr_back=0` — no QR code visible on back
|
||||
|
||||
### 7. Font Size Controls
|
||||
|
||||
- [ ] Manual font size override (+ / − in controls panel) changes the badge render live
|
||||
- [ ] Change is visible on the physical printed card
|
||||
- [ ] Reset (↺) returns to auto-sizing
|
||||
- [ ] Auto-sizing produces a reasonable default for all test names without manual adjustment
|
||||
|
||||
### 8. Edit Fields at Kiosk
|
||||
|
||||
- [ ] Badge info editable before printing: change full_name_override, verify change appears on card
|
||||
- [ ] Save change → re-print → new value printed
|
||||
- [ ] Cancel reverts to saved value
|
||||
- [ ] Badge type dropdown changes footer stripe color on the rendered badge
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations on Test Day
|
||||
|
||||
These are not bugs — just gaps that won't be addressed during the test day:
|
||||
|
||||
- **`style_href` external CSS**: Must be wired before test day (see pre-checklist). If not
|
||||
done, client-specific CSS from the template won't load — fall back to default styles.
|
||||
- **`duplex` backend field**: Not yet in the backend schema. Single-sided behavior depends
|
||||
on the PVC CSS hiding `.badge_back` in print. Verify manually.
|
||||
- **Per-template print margins (`print_margin_cfg`)**: UI doesn't exist yet. If the card
|
||||
needs a physical offset for the Zebra's feed, apply a manual CSS tweak to the PVC layout
|
||||
CSS file and revert after testing.
|
||||
- **Kiosk attendee editing (TASK 4.0)**: Edit panel is currently trusted_access only.
|
||||
Attendee self-edit at kiosk isn't finished — staff will need to do all edits on test day.
|
||||
- **`@page { size }` in Chrome**: Chrome ignores the CSS page size for Save as PDF.
|
||||
For physical Zebra printing the driver controls paper size — this is fine.
|
||||
|
||||
---
|
||||
|
||||
## Known Print Dialog Behavior (for reference)
|
||||
|
||||
| Browser | Save to PDF | Physical Printer |
|
||||
|---|---|---|
|
||||
| Firefox | Paper size locked to CSS `@page { size }` ✅ | Can select paper size in dialog |
|
||||
| Chrome | Paper size = system default (letter/A4) ❌ | Can select paper size under "More settings" |
|
||||
|
||||
| Chrome Margin Setting | Result |
|
||||
|---|---|
|
||||
| Default | ❌ Inserts URL/date/page chrome, offsets badge centering |
|
||||
| None | ✅ Badge centered correctly |
|
||||
| Minimum | ✅ Badge centered correctly |
|
||||
|
||||
---
|
||||
|
||||
## Things to Note / Capture During Testing
|
||||
|
||||
Use this section to log observations on the day:
|
||||
|
||||
```
|
||||
DATE: ___________
|
||||
|
||||
Driver version: ___________
|
||||
Card stock type: ___________
|
||||
CUPS printer name: ___________
|
||||
|
||||
Observations:
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
Font size adjustments needed:
|
||||
Name default: was __px, adjusted to __px
|
||||
Title default: was __px, adjusted to __px
|
||||
Affiliations: was __px, adjusted to __px
|
||||
Location: was __px, adjusted to __px
|
||||
|
||||
Physical offset needed (crop/margin): ___________
|
||||
|
||||
Bugs found:
|
||||
-
|
||||
-
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Follow-Up After Test Day
|
||||
|
||||
- [ ] Update font size defaults in `ae_comp__badge_print_controls.svelte` based on observations
|
||||
- [ ] Note any physical margin/offset needed into `cfg_json: { print_margin: {...} }` once that UI exists
|
||||
- [ ] Document driver version and CUPS config that worked in this file
|
||||
- [ ] Commit any fixes, re-run `npx svelte-check`, commit clean
|
||||
@@ -0,0 +1,45 @@
|
||||
**AE Firefly Theme Repair — Summary (Recovery & Integration Complete)**
|
||||
|
||||
- **Summary**: Investigation, targeted repair, and successful integration of the AE Firefly theme family and key UI components. This document records the final resolution, the migration of the Svelte 5 Modal component, and the validation of the `ae_app_3x_llm` branch as the project's stable baseline.
|
||||
|
||||
### 1. Root Cause Resolution (Themes)
|
||||
- **Variables outside selectors**: Fixed. Custom-property declarations in `src/ae-firefly*.css` were moved into proper `[data-theme='AE_Firefly*']` selector blocks.
|
||||
- **Variable precedence**: Resolved by enforcing `--background: ... !important` within the theme-specific selectors to ensure they override global `:root` defaults.
|
||||
- **Files Repaired**:
|
||||
- `src/ae-firefly.css`
|
||||
- `src/ae-firefly-steelblue.css`
|
||||
- `src/ae-firefly-indigo.css`
|
||||
- `src/ae-firefly-rainbow.css`
|
||||
|
||||
### 2. Actions Taken (Recovery Phase)
|
||||
- **Surgical Integration**: Selective harvesting of "90% done" work from WIP branches while preserving the integrity of the Lucide migration and Svelte 5 baseline.
|
||||
- **`element_modal_v1.svelte`**:
|
||||
- Successfully imported from `wip-modal-fix-attempt`.
|
||||
- **Full Refactor**: Migrated from deprecated `svelte/legacy` and `<slot>` patterns to **Svelte 5 Snippets** and `{@render}` tags.
|
||||
- Verified and tested as the new standard modal component.
|
||||
- **Selective Vetting**:
|
||||
- **Abandoned**: `element_input_v2.svelte` and legacy Badge View v1 (rejected due to legacy FontAwesome regressions and high error counts).
|
||||
- **Removed**: Redundant `element_data_store_v2.svelte` (superseded by `v3`).
|
||||
- **Kept**: Clean, Lucide-based versions of all core components already present on `ae_app_3x_llm`.
|
||||
|
||||
### 3. Repository State (Final Validation)
|
||||
- **Baseline**: `ae_app_3x_llm` is now the unified, verified "known-good" state.
|
||||
- **Validation**: `npx svelte-check` performed on the merged state returned **0 errors and 0 warnings**.
|
||||
- **Cleanup**: Temporary integration branches have been deleted.
|
||||
- **Backups**: `wip-modal-fix-attempt` and `wip/theme-fix` remain as reference points but are no longer active.
|
||||
|
||||
### 4. Merged Files (Key Updates)
|
||||
- `src/ae-firefly*.css` (Repaired themes)
|
||||
- `src/lib/elements/element_modal_v1.svelte` (New Svelte 5 Modal)
|
||||
- `documentation/PROJECT__AE_Firefly_Theme_Repair_SUMMARY.md` (This document)
|
||||
|
||||
### 5. Final Status
|
||||
- **Status**: **COMPLETE / STABLE**
|
||||
- **Branch**: `ae_app_3x_llm`
|
||||
- **Verification**: Verified via `svelte-check` and theme inspections.
|
||||
|
||||
---
|
||||
*Prepared by: Gemini CLI (March 17, 2026)*
|
||||
|
||||
---
|
||||
*Archival note (2026-03-20): `element_modal_v1.svelte` (referenced in §2 as "new standard modal") was subsequently retired — it had zero active importers. Modal usage in the codebase relies on Flowbite `<Modal>` component. See `AE__UI_Component_Patterns.md` §11.*
|
||||
@@ -0,0 +1,163 @@
|
||||
PRES-MGMT Session View — Refactor Plan
|
||||
|
||||
**STATUS: ✅ RESOLVED (2026-02-26)**
|
||||
|
||||
## Resolution Summary
|
||||
|
||||
The "refresh twice" bug was fixed by addressing **two root causes** in the nested data loader chain:
|
||||
|
||||
1. **Disabled caching in nested loads**: Changed `try_cache: false` to `try_cache` (preserve parent value) in:
|
||||
- `ae_events__event_session.ts` → `_refresh_session_id_background()`
|
||||
- `ae_events__event_presentation.ts` → `_refresh_presentation_li_background()`
|
||||
|
||||
2. **Missing microtask yields**: Added `await Promise.resolve()` after each `db_save_ae_obj_li__ae_obj()` call to ensure Dexie liveQuery observers fire before functions return.
|
||||
|
||||
3. **Fire-and-forget nested loads**: Changed `forEach()` to `await Promise.all()` in presentation loader to block until all presenter loads complete.
|
||||
|
||||
**Result:** Session view now renders presentations AND presenters on first load without manual refresh.
|
||||
|
||||
**Performance Impact:** Adds ~100-200ms to initial navigation (time to write 1-5 presenter records + microtask yields), but guarantees correct first-render.
|
||||
|
||||
**Files Modified:**
|
||||
- `src/lib/ae_events/ae_events__event_session.ts` (lines 100-107)
|
||||
- `src/lib/ae_events/ae_events__event_presentation.ts` (lines 187-198)
|
||||
- `src/lib/ae_events/ae_events__event_presenter.ts` (line 157-161)
|
||||
|
||||
**Documentation Updated:**
|
||||
- `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md` - Added "try_cache: false" bug section with detailed explanation
|
||||
- Code comments added explaining the critical fixes in all modified loader functions
|
||||
- Journals module updated with microtask yields for consistency (already preserved try_cache correctly)
|
||||
|
||||
**Test Status:**
|
||||
- Manual testing: ✅ Confirmed working (session + presentations + presenters render on first load)
|
||||
- Playwright test: Created at `tests/coldstart_event_session.test.ts` (requires valid session ID to run)
|
||||
|
||||
**Lessons Learned:**
|
||||
1. **Always preserve `try_cache` through nested data loads** - Disabling caching at any level breaks the entire chain
|
||||
2. **Add microtask yields after IndexedDB writes** - Ensures liveQuery observers fire before functions return
|
||||
3. **Block on nested loads with `await Promise.all()`** - Fire-and-forget forEach() causes race conditions
|
||||
4. **Journals module was already correct** - It preserved `try_cache` for nested entry loads; only added yields for consistency
|
||||
5. **The existing blocking pattern was sufficient** - No special helper function needed; the bug was in the loader implementation, not the architecture
|
||||
|
||||
**What We Implemented:**
|
||||
We effectively implemented **Option A (Blocking Hydration)** but at a lower level than originally planned. Instead of creating a new `load_session_with_relations()` helper, we fixed the existing loader chain to properly block and cache nested data. The `+page.ts` already used `await` for the session load - it just wasn't working because the underlying loaders weren't caching nested data.
|
||||
|
||||
---
|
||||
|
||||
## Original Plan (For Reference)
|
||||
|
||||
Goal
|
||||
|
||||
Make the Presentation Management Session view deterministic on cold-start (empty IndexedDB). The page must render Presentations, Presenters, and Hosted Files without requiring manual refreshes.
|
||||
|
||||
Constraints
|
||||
|
||||
- Svelte 5 runes and Dexie `liveQuery` behavior (observable recreation, subscription timing).
|
||||
- Minimize user-perceived latency — keep navigation snappy where possible.
|
||||
- Avoid large architectural changes unless necessary.
|
||||
|
||||
Options (high level)
|
||||
|
||||
A) Blocking Hydration (recommended for correctness)
|
||||
- Block the route `+page.ts` load until the session and all directly required related objects are fetched from the backend and written into IndexedDB. Return `initial_session_obj` in the load data for immediate rendering.
|
||||
- Pros: simplest to guarantee first-draw correctness; minimal component changes.
|
||||
- Cons: adds latency to navigation (can be mitigated with optimistic UI or progress indicator).
|
||||
|
||||
B) Prefetch Related Records + Hydrate Fallback (hybrid)
|
||||
- Non-blocking load but `+page.ts` returns `initial_session_obj` and small related-objects payloads (presentations, presenter IDs, hosted_file metadata). Components use these fallbacks while `liveQuery` takes over.
|
||||
- Pros: keeps navigation responsive; often sufficient.
|
||||
- Cons: requires careful payload shaping and DB write ordering.
|
||||
|
||||
C) Explicit Dependency Chaining in UI (advanced)
|
||||
- Keep non-blocking loads and use explicit dependency chaining: write session -> await write completion -> then write presentations -> await -> then presenters, ensuring microtask queue flushes between writes. Use targeted `liveQuery` re-creation only when upstream dependency fully resolved.
|
||||
- Pros: minimal route latency; deterministic ordering.
|
||||
- Cons: more complex to implement and test.
|
||||
|
||||
Recommendation
|
||||
|
||||
Start with Option A (Blocking Hydration) for the session page to restore deterministic behavior quickly. After correctness is achieved, consider converting to Option B or C for improved perceived performance if needed.
|
||||
|
||||
Detailed Steps (Option A - Blocking Hydration)
|
||||
|
||||
1) Add a small helper in `events_func` (e.g., `load_session_with_relations`) that:
|
||||
- fetches session by ID from API
|
||||
- fetches related presentations (limit/filters as needed)
|
||||
- fetches presenters referenced by those presentations (deduplicate IDs)
|
||||
- fetches hosted_file metadata for presentation files (if required for the view)
|
||||
- writes all results to IndexedDB in a controlled order (session -> presentations -> presenters -> hosted_files)
|
||||
- returns a compact `initial_session_obj` payload containing fields needed for first-draw (session, presentation list, presenter summary)
|
||||
|
||||
Implementation note: Use `await db.transaction('rw', db_events.session, db_events.presentation, db_events.presenter, async () => {...})` if atomicity helps. Alternatively write in sequential awaits and call `await Promise.resolve()` after each write to let the microtask queue settle.
|
||||
|
||||
2) Update route loader: `src/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/+page.ts` (create if missing) to call and `await` the helper, then return `initial_session_obj` on `data`.
|
||||
|
||||
Example pseudo-code:
|
||||
|
||||
export async function load({ params, parent }) {
|
||||
const data = await parent();
|
||||
if (browser) {
|
||||
const init = await events_func.load_session_with_relations({ api_cfg: data[data.account_id].api, session_id: params.session_id, log_lvl: 0 });
|
||||
data.initial_session_obj = init;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
3) Ensure the page component `+page.svelte` uses the `initial_session_obj` as immediate fallback (it already does in Aether).
|
||||
|
||||
4) Add instrumentation logs inside `liveQuery` closures and the helper to verify ordering during QA.
|
||||
|
||||
5) Add tests (see below) and manual verification steps.
|
||||
|
||||
Alternative (Option B - Hybrid) Implementation Notes
|
||||
|
||||
- If you cannot block the route, return an `initial_session_obj` that includes minimal related object arrays (IDs + small metadata) and have `+page.svelte` write those into IDB before mounting heavy child components.
|
||||
- Use `untrack()` to set selection IDs so stores are updated without causing premature reactivity loops.
|
||||
|
||||
Explicit Dependency Chaining (Option C) Notes
|
||||
|
||||
- Implement a single `prefetch` function that sequentially performs writes and `await Promise.resolve()` between stages.
|
||||
- For debugging, add microtask delays (e.g., `await 0`) between writes to observe behaviour.
|
||||
|
||||
Testing and Verification
|
||||
|
||||
1) Integration test (Playwright recommended)
|
||||
- Clear IndexedDB for the app origin.
|
||||
- Navigate to `/events/<event_id>/.../session/<session_id>` and assert that the presentation list and presenters are visible within N ms without manual refresh.
|
||||
- Repeat on subsequent navigations to ensure no regressions.
|
||||
|
||||
2) Unit tests
|
||||
- For `events_func.load_session_with_relations`, stub API responses and assert DB writes are made in expected order.
|
||||
|
||||
3) Manual QA
|
||||
- With a cold profile or after clearing Site storage, navigate to the session page and confirm content is present after the initial navigation and that no manual refreshes are required.
|
||||
|
||||
Migration and Rollout
|
||||
|
||||
- Implement Option A behind a feature flag if you want to control rollout.
|
||||
- Short-term: apply Option A to the single problematic route to reduce blast radius.
|
||||
- Long-term: consider a library-level helper to standardize "blocking prefetch for nested related records" across other pages.
|
||||
|
||||
Rollback Plan
|
||||
|
||||
- Because changes are additive and limited to one route and helper, revert the `+page.ts` modification and helper call to restore prior behavior.
|
||||
|
||||
Deliverables for tomorrow
|
||||
|
||||
- `events_func.load_session_with_relations` helper (TS) + unit tests
|
||||
- Updated `+page.ts` loader for session route to `await` helper and return `initial_session_obj`
|
||||
- Small test harness / Playwright test that reproduces the cold-start issue and verifies the fix
|
||||
- Instrumentation logs temporarily enabled for QA
|
||||
|
||||
Estimated effort
|
||||
|
||||
- Blocking hydration implementation + tests: 2-4 hours
|
||||
- Hybrid or chaining implementations: additional 2-6 hours depending on thoroughness
|
||||
|
||||
Notes about Svelte 5 + Dexie specifics
|
||||
|
||||
- Keep `liveQuery` closures stable; capture primitive IDs rather than reactive objects.
|
||||
- Use `$derived` and `$derived.by` to keep observable instances stable across renders.
|
||||
- Use `untrack()` when setting selection values to avoid premature subscriptions.
|
||||
- After DB writes, allowing the microtask queue to settle (`await Promise.resolve()`) helps ensure observers are notified in the expected order during development and debugging.
|
||||
|
||||
If you want I can implement Option A for the session route tomorrow (create helper, update loader, add test).
|
||||
106
documentation/archive/PROJECT__AE_combined_front_back_Docker.md
Normal file
106
documentation/archive/PROJECT__AE_combined_front_back_Docker.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Project: Unified Aether Platform Orchestration (V3)
|
||||
> **Status: ✅ COMPLETE (2026-03-10)**
|
||||
> `ae_app` is live in `aether_container_env/docker-compose.yml`. Both frontend and backend deploy together via `npm run deploy:staging` / `npm run deploy:prod`. Internal `ae_api` networking active. Healthcheck wired. Old `ae_env_node_app` is superseded (archive when ready).
|
||||
|
||||
> **Goal:** Consolidate the SvelteKit Frontend and FastAPI Backend into a single Docker Compose environment within `aether_container_env`.
|
||||
|
||||
## 1. Overview & Benefits
|
||||
Currently, the platform runs in two separate Docker environments (`ae_env_node_app` and `aether_container_env`). Combining them into one allows for:
|
||||
- **Unified Lifecycle:** Start/Stop/Update the entire stack with one command.
|
||||
- **Internal Networking:** The Frontend (SvelteKit) can communicate with the Backend (FastAPI) via the high-speed internal Docker network (`ae_api:5005`) instead of routing through external IPs.
|
||||
- **Shared Infrastructure:** Single instances of Redis, Dozzle, and Nginx serving both tiers.
|
||||
- **Simplified Deployment:** Reduces the need for sibling directory dependencies on production servers.
|
||||
|
||||
---
|
||||
|
||||
## 2. Target Architecture (Workstation & Prod)
|
||||
The unified environment will reside in `~/OSIT_dev/aether_container_env/`.
|
||||
|
||||
### Directory Mapping
|
||||
- **API Source:** `~/OSIT_dev/aether_api_fastapi` -> Mounted to `ae_api`
|
||||
- **App Source:** `~/OSIT_dev/aether_app_sveltekit` -> Mounted to `ae_app`
|
||||
- **Nginx Config:** `~/OSIT_dev/aether_container_env/conf/nginx/`
|
||||
- **Logs:** `~/OSIT_dev/aether_container_env/logs/`
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation Plan
|
||||
|
||||
### Step 1: Update `aether_container_env/.env`
|
||||
Add these new variables to the master `.env` file:
|
||||
```bash
|
||||
# --- SvelteKit Frontend settings ---
|
||||
AE_APP_SRC=/home/scott/OSIT_dev/aether_app_sveltekit
|
||||
CONTAINER_AE_APP=ae_app_dev
|
||||
AE_APP_REPLICAS=4
|
||||
AE_APP_NODE_PORT=3001
|
||||
# Note: Use internal DNS 'ae_api' for PUBLIC_AE_API_SERVER_INTERNAL
|
||||
```
|
||||
|
||||
### Step 2: Integrate `ae_app` into `docker-compose.yml`
|
||||
Add the consolidated SvelteKit service. This replaces the legacy Flask service:
|
||||
```yaml
|
||||
ae_app:
|
||||
restart: always
|
||||
build:
|
||||
context: ${AE_APP_SRC}
|
||||
dockerfile: Dockerfile
|
||||
target: deploy-node
|
||||
scale: ${AE_APP_REPLICAS}
|
||||
env_file:
|
||||
- ./.env
|
||||
ports:
|
||||
- "${AE_APP_NODE_PORT}:3000"
|
||||
extra_hosts:
|
||||
- "${DOCKER_AE_SERVER_EXTRA_HOST}"
|
||||
- "${DOCKER_AE_APP_SERVER_EXTRA_HOST}"
|
||||
- "${DOCKER_AE_API_SERVER_EXTRA_HOST}"
|
||||
volumes:
|
||||
# Mount source for real-time dev (Optional for production)
|
||||
- ${AE_APP_SRC}:/srv/aether_app
|
||||
- ${HOSTED_FILES_SRC}:/srv/hosted_files
|
||||
- ${HOSTED_TMP_SRC}:/srv/hosted_tmp
|
||||
depends_on:
|
||||
- ae_api
|
||||
- redis
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
```
|
||||
|
||||
### Step 3: Nginx Gateway Configuration
|
||||
Update (or create) the Nginx template in `conf/nginx/` to route traffic to the internal `ae_app` service.
|
||||
|
||||
**Example Upstream Block:**
|
||||
```nginx
|
||||
upstream svelte_backend {
|
||||
# Internal Docker DNS handles load balancing across replicas
|
||||
server ae_app:3000;
|
||||
# ip_hash; # Enable if session persistence is needed
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Verification & Migration
|
||||
1. **Healthcheck:** Ensure `src/routes/health/+server.ts` is active.
|
||||
2. **Internal Proxy Test:** Update SvelteKit's `PUBLIC_AE_API_SERVER` to `ae_api` (internal) and verify connectivity.
|
||||
3. **Clean Up:** Once verified, the old `ae_env_node_app` directory can be archived.
|
||||
|
||||
---
|
||||
|
||||
## 4. Scaling & Performance
|
||||
- **API Scaling:** Controlled by `AE_API_REPLICAS`.
|
||||
- **App Scaling:** Controlled by `AE_APP_REPLICAS`.
|
||||
- **Memory Management:** Each replica is isolated. Shared state (caching) is handled via the internal **Redis** service.
|
||||
- **Healthchecks:** Docker will automatically restart any `ae_app` replica that fails the `/health` check.
|
||||
|
||||
---
|
||||
|
||||
## 5. Deployment Commands (Unified)
|
||||
```bash
|
||||
# From aether_container_env/
|
||||
docker compose up -d --build --remove-orphans
|
||||
docker compose ps
|
||||
docker compose logs -f ae_app
|
||||
```
|
||||
31
documentation/archive/TODO__Agents__ARCHIVE_2026-03.md
Normal file
31
documentation/archive/TODO__Agents__ARCHIVE_2026-03.md
Normal file
@@ -0,0 +1,31 @@
|
||||
## ✅ Completed (2026-03)
|
||||
- [x] **[Stores] Phase 1 — Dead code cleanup** (`ae_stores.ts`, `ae_events_stores.ts`, `ae_idaa_stores.ts`): removed `ver_idb`, stale comments, `console.log` lines, Stripe button block (zero consumers), personal Novi UUIDs, dead alternatives. Net: −202 lines across 3 files. svelte-check: 0 errors. (2026-03-16)
|
||||
- [x] **[Stores] Phase 2a — Split defaults into domain sub-files**: `ae_stores__auth_loc_defaults.ts`; `ae_events_stores__badges/launcher/leads/pres_mgmt_defaults.ts`. Spread-merged back into store structs — zero consumer changes. (2026-03-16)
|
||||
- [x] **[Stores] Phase 2b — TypeScript interfaces for defaults sub-files**: `SiteCfgJson`, `AePerson`, `AeUser`, `AccessType`, `AuthLocState`; `BadgesLocState/SessState`; `SectionState`, `LauncherLocState/SessState`; `LeadsLocState/SessState`, `TmpLicense`; `PresMgmtLocState/SessState`. svelte-check: 0 errors. (2026-03-16)
|
||||
- [x] **[UI]** Style Review Phase 1 & 2 complete — all non-frozen, non-IDAA routes migrated: FA→Lucide (events, pres_mgmt, core, badges, leads, hosted_files), `variant-*`→`preset-*` (all modules), `code_to_html` badge dict refactored to Lucide component map, FA CDN scoped to IDAA layout, global `svg.lucide { display: inline }` CSS rule added to fix icon inline flow. See `documentation/PROJECT__AE_Style_Review.md`. (2026-03-16)
|
||||
- [x] **[UI]** Pres Mgmt Phase 3 — FA→Lucide icon migration across all 24 pres_mgmt files. (2026-03-16)
|
||||
- [x] **[IDAA]** `ae_idaa_comp__event_obj_id_edit.svelte` — inlined Tailwind utilities, removed `<style>` block; eliminated all 23 `@apply`/`@reference` svelte-check warnings. (2026-03-16)
|
||||
- [x] **[Badges]** Badge print page svelte-check fix: extracted print CSS to `static/ae-print-badge.css`; fixed unclosed `<script>` tag in `print/+page.svelte`. (2026-03-16)
|
||||
- [x] **[Svelte/Tests]** svelte-check cleanup: fixed `select_ref_badge_type` `$state()` declaration; two `<svelte:component>` deprecations in launcher components; `page.evaluate()` two-arg pattern in `badge_print_layout.test.ts`. (2026-03-16)
|
||||
- [x] **[Launcher]** Hosted file download button `require_auth` prop — added `require_auth?: boolean` (default `true`) to `ae_comp__hosted_files_download_button.svelte`; all existing consumers unchanged. Launcher `launcher_file_cont.svelte` passes `require_auth={false}` so unauthenticated kiosk users can open/download files without being blocked. (2026-03-16)
|
||||
- [x] **[Security]** `PUBLIC_AE_API_SECRET_KEY` audit complete. Key is `PUBLIC_*` by design (always in client bundle). Highest-risk anonymous path uses limited-permission `PUBLIC_AE_BOOTSTRAP_KEY`. Full server-side migration not justified given JWT + account_id auth layers. Current state acceptable. (2026-03-11)
|
||||
- [x] **[UX]** Session Expired banner — `ae_auth_error` store wired to API helpers; root layout sets `flag_expired` on 401/403; non-blocking dismissible banner rendered. (2026-03-12)
|
||||
- [x] **[UX]** Access Denied UI standardized — `element_access_denied.svelte` created; `/core` layout, `/events/settings`, and `/events/badges/review` updated to use it. (2026-03-12)
|
||||
- [x] **[Build]** Rollup/Vite circular dependency warnings eliminated — `manualChunks` in `vite.config.ts` colocates all `svelte/*` internals into a single `svelte-vendor` chunk, preventing `runtime.js` / `index-client.js` split (~35 warnings gone). (2026-03-11)
|
||||
- [x] **[Refactor]** `try_cache` audit + sponsorship/event_file/hosted_file SWR alignment — removed vestigial `try_cache` params from `generate_qr_code`, `ae_core_functions` wrappers; added SWR fast/slow path to sponsorship loaders; changed `event_file` and `hosted_file` single-object loader defaults from `false` → `true` for consistency. (2026-03-11)
|
||||
- [x] **[DevOps]** Frontend + Backend unified into single `aether_container_env` Docker Compose. `ae_app` service live with healthcheck, single exposed port (`AE_APP_NODE_PORT`), internal `ae_api` networking. Deploy scripts in `package.json` both target `../aether_container_env/docker-compose.yml`. (2026-03-10)
|
||||
- [x] **[DevOps]** `/health` endpoint live at `src/routes/health/+server.ts`. Docker `HEALTHCHECK` uses it. (2026-03-10)
|
||||
- [x] **[UI]** Dark mode `color-scheme` fix — `html.dark/light { color-scheme }` in `app.css`; all native browser controls now sync to app dark mode. (2026-03-10)
|
||||
- [x] **[Launcher]** Location select → session auto-load bug fixed via `$derived.by()` liveQuery pattern. (2026-03-10)
|
||||
- [x] **[Svelte]** `state_referenced_locally` warning fixes — 10 warnings resolved in IDAA archives/BB. (2026-03-09)
|
||||
- [x] **[TypeScript]** Sign In/Out TS errors fixed — `user_id` / `person_id` typed as `string | null`. (2026-03-09)
|
||||
- [x] **[Tests]** All badge data integrity and attendee workflow Playwright tests passing. Root causes documented in `tests/README.md`. (2026-03)
|
||||
- [x] **[Badges]** Badge print controls panel, QR code, duplex wiring, review form, print button, multi-word fulltext search, `data-testid` attributes. (2026-03)
|
||||
- [x] **[UI]** Firefly Theme + Pres Mgmt Visual Redesign (5 files). (2026-03-06)
|
||||
- [x] **[Docs]** UI Style Guidelines + Component Patterns docs created. (2026-03-06)
|
||||
- [x] **[API]** V3 Lookup system integration; Event File V3 mapping; `event_session` search 400-error fix. (2026-02/03)
|
||||
- [x] **[API]** All CRUD helpers on V3 `/v3/crud/...` paths. (2026-02)
|
||||
- [x] **[Security]** Purged `x-aether-api-token`; fixed misplaced CORS headers; Account ID Scavenging. (2026-02)
|
||||
- [x] **[Security]** Playwright integration tests replace `verify_jwt_logic.js` simulation tests. (2026-03)
|
||||
- [x] **[Framework]** `AE_Obj_Field_Editor_V3` with Svelte 5 Runes. CRUD v2 fully retired. (2026-03-05)
|
||||
- [x] **[IDAA]** Bulletin Board and Recovery Meetings functionality verified. (2026-02)
|
||||
227
documentation/archive/TODO__Agents__ARCHIVE_2026-04.md
Normal file
227
documentation/archive/TODO__Agents__ARCHIVE_2026-04.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Frontend Agent Task List
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** Stable — ongoing development.
|
||||
|
||||
|
||||
## 🚧 Upcoming High Priority
|
||||
|
||||
### [Stores] Svelte 4 → Svelte 5 State Migration (prerequisite for Phase 2c)
|
||||
The app uses `svelte-persisted-store` (Svelte 4 store contract) for all core persisted state
|
||||
(`ae_loc`, `idaa_loc`, `ae_api`, `ae_sess`, etc.). In Svelte 5 `$effect`, reading **any field**
|
||||
of a Svelte 4 store subscribes to the **entire store** — coarse-grained reactivity. This is the
|
||||
root cause of the IDAA Novi re-auth bug (2026-03-30): unrelated `$ae_loc` writes (e.g. iframe
|
||||
height, SWR cfg reload) triggered the Novi verification effect repeatedly.
|
||||
|
||||
Migration target: replace `svelte-persisted-store` with Svelte 5 `$state`-based persistence
|
||||
(e.g. `runed` `PersistedState`, or a lightweight custom wrapper). This gives fine-grained
|
||||
reactivity — only effects that actually read a changed field re-run.
|
||||
|
||||
**Phased approach (do NOT do all at once):**
|
||||
|
||||
- [ ] **Phase A — Project plan + wrapper decision:** Write `PROJECT__Stores_Svelte5_Migration.md`.
|
||||
Decide: `runed` library vs. custom `$state` + localStorage wrapper. Audit all store consumers.
|
||||
Identify stores in priority order. Estimate blast radius per store.
|
||||
|
||||
- [ ] **Phase B — Core auth stores (highest impact, start here):**
|
||||
- `ae_loc` (persisted) — auth flags, site cfg, UI state; ~471 consumer sites across 150+ files
|
||||
- `idaa_loc` (persisted) — Novi auth, IDAA query prefs
|
||||
These two cause the most reactive noise. Migrating them also unlocks Phase 2c (separate `ae_auth`
|
||||
store) since the callsite sweep is now required anyway.
|
||||
|
||||
- [ ] **Phase C — Remaining persisted stores:**
|
||||
- `ae_api` (persisted) — API config / JWT
|
||||
- `ae_events_stores` persisted entries (badges, launcher, leads, pres_mgmt loc stores)
|
||||
|
||||
- [ ] **Phase D — Non-persisted writable stores:**
|
||||
- `ae_sess`, `idaa_sess`, `slct`, `slct_trigger`, `ae_auth_error`, `ae_trig`, `ae_snip`, etc.
|
||||
- Lower urgency (no localStorage churn), but fine-grained reactivity still beneficial.
|
||||
|
||||
- [ ] **Phase E — Phase 2c (unblocked after B):** Split `ae_loc` into `ae_auth` + `ae_app`
|
||||
(see entry below — ~471 callsites, but sweep is cheap once already touching every consumer).
|
||||
|
||||
**Project plan doc needed:** Yes — scope is app-wide. Do NOT start Phase B without Phase A.
|
||||
|
||||
---
|
||||
|
||||
### [Stores] Refactor — Phase 2c (deferred)
|
||||
Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining:
|
||||
|
||||
- [ ] **Phase 2c — Actual separate stores (`ae_auth`, `ae_app`):** Requires touching ~471
|
||||
`$ae_loc.*` auth-field read sites across 150+ files. Deferred until a Svelte runes migration
|
||||
of the store layer itself (touching every component anyway makes the callsite sweep cheap).
|
||||
|
||||
### [Backend] Join event_location_id onto event_presenter API view
|
||||
The `event_presenter` object currently has `event_session_id` but not `event_location_id`.
|
||||
When navigating from the Presenter View to the Launcher, the frontend has to do a secondary
|
||||
session lookup to discover the location (magic redirect in launcher base `+page.svelte`).
|
||||
Joining `event_session.event_location_id` into the presenter view/response would let the
|
||||
frontend pass the location directly in the Launcher URL without the extra lookup.
|
||||
- [x] Backend: added `event_location_id` (and `event_location_id_random`) to the `event_presenter` view or API response (2026-04-09)
|
||||
- [x] Frontend: updated `ae_EventPresenter` type and `properties_to_save`; now pass as `events__launcher_id` in `presenter_page_menu.svelte` (2026-04-09)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### [TypeScript] svelte-check hidden errors — discovered 2026-03-27
|
||||
**HOW WE FOUND THIS:** The `@lucide/svelte` 0.577.0 update (2026-03-10) dropped `class` from
|
||||
`IconProps`. Fixing it required a `declare module '@lucide/svelte'` augmentation. That
|
||||
augmentation was mistakenly placed in `app.d.ts`, which is a *script-context* declaration file
|
||||
(no `export {}`). In that context, `declare module` is an **ambient replacement**, not a merge —
|
||||
it wiped all icon exports from svelte-check's view, surfacing 1368 previously hidden errors.
|
||||
Once moved to `src/lucide-augment.d.ts` (a proper module file with `export {}`), the masking
|
||||
lifted and the real pre-existing errors became visible.
|
||||
|
||||
**Lesson:** A broken ambient declaration can silently hide unrelated errors. If svelte-check
|
||||
suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a package's types.
|
||||
|
||||
**Current state (2026-03-31):** 32 errors, 0 warnings — all `ModalProps.children`.
|
||||
|
||||
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.** The flowbite-svelte
|
||||
`Modal` component API changed; `children` is no longer a direct prop (now Svelte snippet-based).
|
||||
Affected files span journals, pres_mgmt, events/settings, and IDAA archives.
|
||||
Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list.
|
||||
Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs.
|
||||
|
||||
- [ ] **[IDAA] Make `contact_li_json_ext` searchable — Recovery Meeting contact search (2026-04-08)**
|
||||
Members cannot search for meetings by contact name or email. `contact_li_json` data is not
|
||||
included in `default_qry_str` and MariaDB cannot substring-search a JSON longtext directly.
|
||||
The `event` table already has `contact_li_json_ext` (STORED GENERATED, indexed) to work around this.
|
||||
|
||||
**Backend (blocked on this first):** Add `contact_li_json_ext` to the searchable fields
|
||||
whitelist for the `event` object type — likely a one-line change in `ae_obj_types_def.py`
|
||||
or the event object definition. Message sent to backend agent 2026-04-08.
|
||||
|
||||
**Frontend (after backend ships):**
|
||||
- `src/lib/ae_events/ae_events__event.ts` → `search__event()`: add `contact_li_json_ext`
|
||||
as an OR condition alongside `default_qry_str` when `qry_str` is present.
|
||||
- `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte` fast-path IDB filter: parse
|
||||
`contact_li_json` and include contact names/emails in the local text match check.
|
||||
|
||||
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage in other event search pages.**
|
||||
The backend was updated 2026-03-31 to expose `default_qry_str` in API responses.
|
||||
Frontend fix applied to Recovery Meetings (`+page.svelte` + `properties_to_save`).
|
||||
Check all other event search pages that use `db_events.event.filter()` or a secondary
|
||||
post-API text filter — they may have the same mismatch (local searches `name`/`description`
|
||||
only while server uses `default_qry_str`). Start with: any route under `/events/` or `/idaa/`
|
||||
that has a full-text search input.
|
||||
|
||||
- [x] **[package.json] Remove orphaned ShadCN/bits-ui packages.** `shadcn-svelte` and `bits-ui`
|
||||
remain in `package.json` but have no usages — `src/lib/components/ui/` was removed 2026-03-27
|
||||
(trashed to `~/tmp/gemini_trash/shadcn_components_ui_2026-03-27`). Removed from `package.json` and
|
||||
`package-lock.json` on 2026-04-02.
|
||||
|
||||
### [IDAA] Jitsi config editor + live site fix
|
||||
- [ ] **Fix live site (id=17) `jitsi_token_endpoint` pointing to dev-api:** DB has
|
||||
`https://dev-api.oneskyit.com/api/jitsi_token` for both site 10 and site 17 (IDAA live).
|
||||
Need to update site 17 in **production** to `https://api.oneskyit.com/api/jitsi_token`.
|
||||
SQL: `UPDATE site SET cfg_json = JSON_SET(cfg_json, '$.jitsi_token_endpoint', 'https://api.oneskyit.com/api/jitsi_token') WHERE id = 17;`
|
||||
|
||||
- [ ] **Add IDAA Jitsi config editor UI** to the jitsi_reports page (administrator_access only),
|
||||
alongside the existing Jitsi URL Builder section. Should allow editing key fields in
|
||||
`site_cfg_json` without needing phpMyAdmin:
|
||||
- `jitsi_token_endpoint` — the JWT signing endpoint (needs to point to prod)
|
||||
- Jitsi domain default (currently hardcoded as `jitsi.dgrzone.com` fallback in the page)
|
||||
- `novi_jitsi_mod_li` — list of Novi UUIDs who get moderator privileges
|
||||
Read from `$ae_loc.site_cfg_json`, PATCH the site record via V3 CRUD
|
||||
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
|
||||
effect without re-login.
|
||||
|
||||
### [PWA] Service worker ignoring `chrome-extension://` requests
|
||||
Browser console shows repeated errors:
|
||||
```text
|
||||
TypeError: Failed to execute 'put' on 'Cache': Request scheme 'chrome-extension' is unsupported
|
||||
```
|
||||
The service worker's fetch/install handler is trying to cache requests with `chrome-extension://`
|
||||
URLs (injected by browser extensions), which the Cache API rejects. Fix: filter out non-`http`/`https`
|
||||
requests before attempting to cache. In the service worker fetch handler, add a guard:
|
||||
```js
|
||||
if (!event.request.url.startsWith('http')) return; // skip chrome-extension:// etc.
|
||||
```
|
||||
Locate in `static/service-worker.js` or the Vite PWA plugin config. Low severity — doesn't break
|
||||
functionality, but pollutes the console and may cause unhandled promise rejections.
|
||||
|
||||
### [Badges] Remaining badge work before first live event
|
||||
- **Badge print controls UX polish:** Scott has improvements in mind — TBD next session.
|
||||
File: `ae_comp__badge_print_controls.svelte`.
|
||||
|
||||
### [CSS] Global placeholder text color — too dark in light mode
|
||||
Placeholder text inherits full input text color in light mode (Tailwind CSS default), making
|
||||
placeholders indistinguishable from filled-in values. Most visible in badge print controls
|
||||
where placeholders show the actual badge value (e.g. "John Smith").
|
||||
|
||||
Workaround: scoped `::placeholder` rule added to `ae_comp__badge_print_controls.svelte`
|
||||
(gray-400 light / gray-500 dark) — `commit 7733ef8`.
|
||||
|
||||
**Long-term fix:** Add a global rule to the main CSS (e.g. `src/app.css` or a theme file):
|
||||
```css
|
||||
::placeholder {
|
||||
color: #9ca3af; /* gray-400 */
|
||||
opacity: 1; /* overrides Firefox's 0.54 default */
|
||||
}
|
||||
.dark ::placeholder {
|
||||
color: #6b7280; /* gray-500 */
|
||||
}
|
||||
```
|
||||
Once the global rule is in place, remove the scoped workaround from the badge controls.
|
||||
|
||||
|
||||
|
||||
### [Leads] Exhibitor Lead Scanning — IN PROGRESS (demo-ready prep)
|
||||
Module is substantially built as a PWA (no Electron). Core flow works end-to-end.
|
||||
Spec: `documentation/PROJECT__AE_Events_Exhibitor_Leads_v3.md` and `_detail.md`.
|
||||
Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_events__exhibit*.ts`.
|
||||
|
||||
**What's working:**
|
||||
- Exhibit search/landing (`/leads/`) — SWR, local + API search, sort
|
||||
- Exhibit detail page — 4-tab layout, sticky header with Add/List toggle, auto-refresh timer
|
||||
- Tab 1 (Start): sign-in via shared passcode OR licensed user (email + passcode)
|
||||
- Tab 2 (Add): QR scan (confirm mode — replaced rapid/qualify) + manual badge search; duplicate/re-enable detection on both
|
||||
- Tab 3 (List): SWR lead list, licensee filter (All / My Leads), sort options, export button
|
||||
- Tab 4 (Manage): admin tools, booth profile edit, passcode, license mgmt, custom questions config, app settings (refresh interval, clear IDB/localStorage, reload)
|
||||
- Lead detail page: view/edit custom question responses, exhibitor notes (TipTap), priority/enable flags
|
||||
- Export wired to V3 action endpoint `/v3/action/event_exhibit/{id}/tracking_export` (CSV/XLSX)
|
||||
|
||||
**Remaining before demo:**
|
||||
- [x] **Export endpoint** — V3 action endpoint confirmed live on backend (2026-03-16). Returns 403 if
|
||||
`leads_api_access` is not enabled on the exhibit — expected behavior. Export button now gated in
|
||||
UI: only renders when `$lq__exhibit_obj?.leads_api_access === true`. Enable via:
|
||||
`PATCH /v3/crud/event_exhibit/{id}` with `{ "leads_api_access": true }`.
|
||||
- [x] **`allow_tracking` gate** — implemented (2026-03-16). QR scanner shows a warning card and
|
||||
blocks the add. Manual search shows a ShieldOff "Opt-Out" badge per row and guards `add_as_lead`.
|
||||
Opt-in model: `allow_tracking` must be explicitly `true` on the badge. Also added `allow_tracking`
|
||||
and `agree_to_tc` to `ae_EventBadge` in `ae_types.ts`.
|
||||
**Demo note:** ensure test badges have `allow_tracking = true` or no one can be added.
|
||||
- [x] **Payment component** — `ae_comp__exhibit_payment.svelte` fully implemented (2026-03-27).
|
||||
Reads Stripe config from `$ae_loc.site_cfg_json` (`stripe_publishable_key`, `stripe_btn_1/3/6/10_license`).
|
||||
License tier selector (1/3/6/10 users) with `{#key}` remount pattern for Stripe web component.
|
||||
3 states: paid confirmation (priority=true), admin setup hint / "contact organizer" (no Stripe config),
|
||||
payment form. `client_reference_id=exhibit_id`. TypeScript declaration in `app.d.ts`.
|
||||
Stripe keys verified visible in `$ae_loc.site_cfg_json` on dev/demo site. Keys need validity check in Stripe dashboard.
|
||||
- [x] **End-to-end smoke test (canceled by client)** — sign in with shared passcode, scan/search a badge, add a lead, view detail, add notes/responses, export CSV; canceled 2026-04-09.
|
||||
- [x] **Install prompt** — PWA install nudge implemented (2026-03-16). `pwa_install.svelte.ts`
|
||||
singleton captures `beforeinstallprompt` (Chrome/Android/desktop) and detects iOS Safari
|
||||
for manual "Share → Add to Home Screen" instructions. Reusable `element_pwa_install_prompt.svelte`
|
||||
placed on the Leads Start tab between the feature grid and sign-in. `pwa_install.init()` wired
|
||||
into root `+layout.svelte`; dismiss persists 7 days via localStorage. svelte-check: 0 errors.
|
||||
|
||||
### [DevOps] Remaining deployment items
|
||||
- [x] **Wire AE_APP_REPLICAS:** `docker-compose.yml` line 147 already has `scale: ${AE_APP_REPLICAS:-1}`. (verified 2026-03-11)
|
||||
- [x] **Archive ae_env_node_app:** Archived as tar.gz under `~/OSIT_dev/backups/`; old history/docs moved to `~/OSIT_dev/for_reference_only/`. (2026-03-11)
|
||||
- [x] **Build Optimization:** Current state finalized. Local Gitea instance stood up at `git.dgrzone.com` (Docker, home server) — future: migrate repos from Bitbucket, verify Backblaze/restic backups cover Gitea data. (2026-03-11)
|
||||
- [x] **Remote deploy script:** `aether_container_env/deploy.sh` — SSH-triggered from workstation via `npm run deploy:remote:test/prod`. Handles git pull (ff-only) + docker build + restart. Tested and working on test env. (2026-03-25)
|
||||
- [x] **`.env.default` cleanup:** Removed 16 dead variables, added missing `AE_NETWORK_NAME`/`CONTAINER_DOZZLE`/`AE_DOZZLE_PORT`, parameterized all container names (`CONTAINER_MARIADB`, `CONTAINER_PMA`, `CONTAINER_AE_OPS`) with `:-default` fallbacks in compose. ("Dozzle" = log viewer container.) (2026-03-26)
|
||||
- [x] **Prod deploy:** Run `npm run deploy:remote:prod` (off-peak). Prerequisites: both repos pushed to Bitbucket ✓; verify `.env.prod` exists in `/srv/apps/prod_aether_app_sveltekit/` on Linode before running. (2026-03-30)
|
||||
- [x] **Bitbucket → SSH migration:** Switched all three repos (`aether_app_sveltekit`, `aether_container_env`, `aether_api_fastapi`) to SSH remotes (`git@bitbucket.org`) on workstation. App passwords deprecated — SSH unaffected. (2026-03-27)
|
||||
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from same branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but should establish proper branch separation (e.g. `main`/`master` for prod).
|
||||
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on Linode → `deploy.sh`. Deferred until Gitea usage is more established.
|
||||
|
||||
|
||||
### [General]
|
||||
- [x] **Temp Cleanup:** `cleanup_tmp_files` wired in `launcher_background_sync.svelte`; called at launcher startup. Confirmed working. (2026-03-11)
|
||||
- [x] **`window.print()` for badge print button:** Wired in `ae_comp__badge_print_controls.svelte` — increments count, fires `window.print()`, redirects to badge search. (done)
|
||||
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
|
||||
|
||||
## ✅ Completed (2026-03)
|
||||
## ✅ Completed (archived)
|
||||
See the full completed history in [documentation/TODO__Agents__ARCHIVE_2026-03.md](documentation/TODO__Agents__ARCHIVE_2026-03.md).
|
||||
Reference in New Issue
Block a user