Compare commits
18 Commits
65e48c764e
...
b7969bc46e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7969bc46e | ||
|
|
99b8eb0b5e | ||
|
|
9e361eae9b | ||
|
|
e735d0c213 | ||
|
|
d05cc63459 | ||
|
|
ac17417f3c | ||
|
|
3773758eb5 | ||
|
|
60bdd2fdba | ||
|
|
72c8f9b502 | ||
|
|
1de87b6c5f | ||
|
|
84a9d0fffc | ||
|
|
87084f0f71 | ||
|
|
de048a084b | ||
|
|
ee79e33a2a | ||
|
|
a5243fa820 | ||
|
|
5fce149808 | ||
|
|
a74effa6ff | ||
|
|
33d48e7e78 |
33
conductor/fix-idaa-breakout-links.md
Normal file
33
conductor/fix-idaa-breakout-links.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Plan: Fix IDAA Jitsi Breakout Links
|
||||
|
||||
IDAA Jitsi meetings are embedded in an iframe on the `idaa.org` website. To allow members to "break out" of the iframe (for a better experience on mobile or to use full-tab features), the app provides an "Open Meeting Externally" link.
|
||||
|
||||
Currently, this link is generated from `$page.url.href`, which often lacks the `key` (site access key) and `uuid` (Novi identity token) required for Aether's authentication gate, especially if the user has navigated internally within SvelteKit.
|
||||
|
||||
## 1. Objective
|
||||
Ensure all "Breakout" and "Copy Link" actions on the IDAA Video Conferences page include the necessary `key` and `uuid` parameters.
|
||||
|
||||
## 2. Implementation Steps
|
||||
|
||||
### Step 1: Update `src/routes/idaa/(idaa)/video_conferences/+page.svelte`
|
||||
- Create a reactive `breakout_url` derived from `$page.url.href`.
|
||||
- In the derivation logic:
|
||||
- Instantiate a `new URL`.
|
||||
- Check if `key` is present in `searchParams`. If missing, pull from `$ae_loc.allow_access` or `$ae_loc.site_access_key`.
|
||||
- Check if `uuid` is present in `searchParams`. If missing, pull from `$idaa_loc.novi_uuid`.
|
||||
- Return the resulting `href`.
|
||||
- Update the following UI elements to use `breakout_url`:
|
||||
- `copy_meeting_link` function (uses `navigator.clipboard.writeText`).
|
||||
- "Open in New Tab" anchor tag (`href`).
|
||||
- "Copy Link" fallback textarea (`value`).
|
||||
- "Copy Break-out Link" in the Jitsi Tools panel (`value`).
|
||||
|
||||
### Step 2: Update `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||
- Add a note in the "Authentication: Novi UUID System" or "Iframe Integration" section about the requirement for `key` and `uuid` in breakout links.
|
||||
- Document that the frontend now automatically re-injects these for external links to ensure persistent session access outside the iframe.
|
||||
|
||||
## 3. Verification
|
||||
- Manually verify the logic:
|
||||
- If `key` and `uuid` are already in the URL (e.g., initial load), the derived URL should remain unchanged (or correctly deduplicated).
|
||||
- If they are missing (e.g., after navigating from another IDAA page), they should be added to the generated link.
|
||||
- Run `npx svelte-check` to ensure no syntax regressions.
|
||||
53
conductor/launcher-ux-refinement.md
Normal file
53
conductor/launcher-ux-refinement.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Plan: Launcher Config UX Refinement (Cohesion & Stability)
|
||||
|
||||
The goal of this plan is to address the visual "bouncing", layering overload, and the misplaced close button in the new Launcher configuration modal.
|
||||
|
||||
## 1. Dimensional Stability
|
||||
- **Problem:** Switching tabs causes the modal to resize vertically and horizontally, leading to a "bouncing" feel.
|
||||
- **Solution:**
|
||||
- Set a fixed height for the `Launcher_cfg` container (e.g., `h-[750px]`).
|
||||
- Use `overflow-y-auto` only for the right-hand content pane.
|
||||
- Ensure the sidebar has a stable width.
|
||||
|
||||
## 2. Visual Hierarchy & Layering
|
||||
- **Problem:** Too many nested backgrounds (Page > Launcher > Modal > Inner Pane > Section Pane > Section Content).
|
||||
- **Solution:**
|
||||
- Flatten the background of the main content pane.
|
||||
- Simplify `Launcher_Cfg_Section.svelte`:
|
||||
- Remove `shadow-xl` from individual sections.
|
||||
- Use subtler borders instead of strong "preset-outlined" colors.
|
||||
- Remove the secondary background (`bg-white/5`) from the section content area.
|
||||
- Standardize on a single, clean surface color for the right-hand pane.
|
||||
|
||||
## 3. The "Centered Close Button" Bug
|
||||
- **Problem:** A close button is appearing in the middle of the screen.
|
||||
- **Investigation:**
|
||||
- Check for absolute-positioned elements in `Launcher_cfg.svelte` or `+layout.svelte`.
|
||||
- Verify if Flowbite's `Modal` default close button is clashing with internal buttons.
|
||||
- **Solution:**
|
||||
- Consolidate all "Close" actions.
|
||||
- Use the Modal's built-in top-right close button (if available) or a single, well-positioned button in the sidebar.
|
||||
|
||||
## 4. Implementation Steps
|
||||
|
||||
### Step 1: Update `Launcher_cfg.svelte`
|
||||
- Set stable dimensions: `h-[750px] max-h-[90vh] w-[1000px] max-w-[95vw]`.
|
||||
- Remove internal shadows and borders that conflict with the Modal container.
|
||||
- Clean up the sidebar "Close" button.
|
||||
|
||||
### Step 2: Update `Launcher_cfg_section.svelte`
|
||||
- Simplify the styling to reduce visual clutter.
|
||||
- Remove `shadow-xl`.
|
||||
- Use consistent padding and margins.
|
||||
|
||||
### Step 3: Update `+layout.svelte`
|
||||
- Ensure the `Modal` is configured for a stable, large view without default padding issues.
|
||||
- Verify the `modal_cfg_open` logic.
|
||||
|
||||
### Step 4: Add `Launcher_cfg_field.svelte` (Helper)
|
||||
- Implement a unified field helper to standardize Label/Description/Input layouts across all tabs.
|
||||
|
||||
## 5. Verification
|
||||
- Toggle between all 7 tabs. Verify zero layout shift (height/width remains constant).
|
||||
- Check appearance in Light and Dark modes.
|
||||
- Verify "Technical Mode" transitions.
|
||||
@@ -397,6 +397,69 @@ These are real incidents — know them before you start.
|
||||
- `timeout = 20000` default (was 60s in PATCH/DELETE until 2026-05-21 — 5-min worst case)
|
||||
- `did_timeout_abort` flag per attempt (separates helper timeouts from caller aborts)
|
||||
|
||||
14. **Account-scoped `liveQuery` trigger firing before bootstrap completes** — components
|
||||
that load account-specific data via `liveQuery` must not trigger the API fetch until the
|
||||
bootstrap Sync Effect in `+layout.svelte` has set the real `account_id`.
|
||||
|
||||
**What happened:** `element_data_store.svelte` triggered its load when `entry` was falsy.
|
||||
On a fresh load with no IDB cache, `$ae_api.account_id` was still `null` (bootstrap hadn't
|
||||
run yet). The `localStorage` scavenge in `api_get_object.ts` then read the stale
|
||||
`account_id = 1` from a previous dev/demo session and made the API call with the wrong
|
||||
account. The response was cached in IDB, and the next page load showed the wrong account's
|
||||
record.
|
||||
|
||||
A second failure mode: if IDB _did_ have a cached record from a previous session with a
|
||||
different account, `liveQuery` returned it as a valid hit (`entry` truthy), so the trigger
|
||||
never fired to fetch the correct record.
|
||||
|
||||
**The fix pattern** for any trigger `$effect` that depends on bootstrapped account context:
|
||||
```typescript
|
||||
$effect(() => {
|
||||
// Use $slct.account_id (non-persisted), NOT $ae_loc.account_id (persisted, stale).
|
||||
// $slct is initialized to null and set only by the bootstrap Sync Effect, so it
|
||||
// reliably gates the fetch until bootstrap has completed.
|
||||
const account_id = $slct.account_id;
|
||||
const api_ready = !!$ae_api?.base_url;
|
||||
const entry = $lq__ds_obj as SomeType | null | undefined;
|
||||
|
||||
if (!browser || !account_id || !api_ready) return;
|
||||
|
||||
// Also re-fetch when IDB holds a record from a different (non-null) account.
|
||||
// null account_id = global/shared fallback — that is still a valid cache hit.
|
||||
const entry_is_stale_account =
|
||||
entry !== undefined &&
|
||||
entry !== null &&
|
||||
entry.account_id !== null &&
|
||||
entry.account_id !== account_id;
|
||||
|
||||
if (!entry || entry_is_stale_account) {
|
||||
trigger = 'load...';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Why `$slct` not `$ae_loc`:**
|
||||
`$ae_loc` is a `svelte-persisted-store` — it hydrates from `localStorage` before any
|
||||
effects run, so its `account_id` may be a stale value from a previous session. `$slct`
|
||||
is a plain writable store initialized to `null`; the bootstrap Sync Effect is the only
|
||||
thing that sets it. Until that runs, `$slct.account_id` is `null`, providing a reliable
|
||||
gate. See `GUIDE__SvelteKit2_Svelte5_DexieJS.md` → "Bootstrap Race" for the Dexie-side
|
||||
context.
|
||||
|
||||
15. **`tmp_sort_*` comparators written descending instead of ascending** — `build_tmp_sort()` encodes `priority=true` as `'0'` and `priority=false` as `'1'`, designed for **ascending** sort so priority items appear first. Writing a JS `.sort()` comparator as `b.localeCompare(a)` (descending) inverts the encoding and sends priority items to the bottom.
|
||||
|
||||
Found in journals (2026-06), IDAA recovery meetings fast-path and API re-sort (2026-06), and as a Dexie anti-pattern in BB post comments.
|
||||
|
||||
```ts
|
||||
// ❌ Wrong — descending puts priority=false ('1') before priority=true ('0')
|
||||
list.sort((a, b) => (b.tmp_sort_1 ?? '').localeCompare(a.tmp_sort_1 ?? ''));
|
||||
|
||||
// ✅ Correct — ascending matches build_tmp_sort encoding
|
||||
list.sort((a, b) => (a.tmp_sort_1 ?? '').localeCompare(b.tmp_sort_1 ?? ''));
|
||||
```
|
||||
|
||||
**Companion Dexie trap:** `collection.reverse().sortBy('tmp_sort_*')` — Dexie ignores a collection-level `.reverse()` when `.sortBy()` is called. The sort is always ascending. To reverse the result, call `.reverse()` on the returned array after `await`. See `GUIDE__SvelteKit2_Svelte5_DexieJS.md` → `build_tmp_sort` section.
|
||||
|
||||
---
|
||||
|
||||
## 8. Source Layout (Quick Reference)
|
||||
|
||||
@@ -157,26 +157,58 @@ This layout hides `.badge_back` in `@media print` — only the front face prints
|
||||
|
||||
---
|
||||
|
||||
### Epson — Fan-Fold / Label Printer
|
||||
### Epson ColorWorks C3500 — Fan-Fold Label Printer
|
||||
|
||||
**Status:** Not yet tested. Section to be filled in after testing.
|
||||
**Card stock:** 4" × 6" fan-fold paper label stock
|
||||
**Layout code:** `badge_4x6_fanfold`
|
||||
**Status:** Configured. First live use: Axonius Adapt DC — June 9, 2026.
|
||||
|
||||
Common Epson models used for fan-fold name badge stock: TM-T88 series, C3500, LX series.
|
||||
Fan-fold stock is typically 4" × 3" or 4" × 6" paper labels.
|
||||
The C3500 is a color inkjet label printer — it prints continuous fan-fold paper stock,
|
||||
not individual cards. Badges are separated along the perforation after printing.
|
||||
|
||||
#### Physical Setup
|
||||
|
||||
- Connect via USB or Ethernet
|
||||
- Load 4" × 6" fan-fold stock per Epson instructions
|
||||
- The C3500 is single-sided — only the front face prints. Back section is suppressed in CSS.
|
||||
- The badge has a lanyard hole punch: 5/8" × 1/8", centered, 1/4" from the top.
|
||||
Most fan-fold stock for badge use includes a pre-punched lanyard slot — verify stock matches.
|
||||
|
||||
#### Driver
|
||||
|
||||
- Epson ColorWorks C3500 CUPS driver available from epson.com (ColorWorks section)
|
||||
- On Linux/CUPS: install the provided PPD and add the printer at `http://localhost:631`
|
||||
- Set default paper size to **4" × 6"** in CUPS
|
||||
- Print a test page from CUPS before going live
|
||||
|
||||
#### Chrome Print Settings (C3500)
|
||||
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
| Destination | Epson C3500 (CUPS name) |
|
||||
| Paper size | 4 × 6 in (set in CUPS driver) |
|
||||
| Margins | **None** |
|
||||
| Background graphics | On |
|
||||
| Pages | 1 (single-sided) |
|
||||
|
||||
#### CSS Layout
|
||||
|
||||
Fan-fold badges would use a layout sized to the specific label stock.
|
||||
A new CSS layout file will need to be created per stock size if not already present.
|
||||
Naming convention: `badge_layout_epson_[model]_[size].css`
|
||||
The C3500 uses the `badge_4x6_fanfold` layout. CSS file:
|
||||
`src/lib/ae_events/badges/css/badge_layout_epson_4x6_fanfold.css`
|
||||
|
||||
#### Setup Notes
|
||||
|
||||
*(To be filled in after testing — cover: driver source, CUPS setup, paper size, Chrome settings)*
|
||||
Created 2026-05-15 for Axonius Adapt DC. Key specs:
|
||||
- `badge_front` 4" × 6", portrait orientation
|
||||
- `badge_header` max-height 1.5in
|
||||
- Lanyard hole: 5/8" × 1/8", centered, 1/4" from top
|
||||
- `@page { size: 4in 6in; margin: 0; }` set in the print page dynamically
|
||||
- `.badge_back` suppressed in `@media print` (single-sided)
|
||||
|
||||
#### Known Behaviors
|
||||
|
||||
*(To be filled in after testing)*
|
||||
- Same Chrome margin rules apply: **Margins → None** prevents URL/date header clipping
|
||||
- Firefox honors `@page { size: 4in 6in }` for PDF proofing — use it to verify layout
|
||||
- Fan-fold stock separates along the perforation — no cutting needed, but verify the
|
||||
perforation lands outside the badge content area
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ server needed.
|
||||
- **Stock:** 3.5" × 5.5" PVC cards.
|
||||
- **Orientation:** Cards face-up, landscape in the hopper.
|
||||
- **Single-Sided:** Only the front face prints; the back section is hidden via CSS.
|
||||
- **Layout code:** `badge_3.5x5.5_pvc`
|
||||
|
||||
### Printer Reference: Epson ColorWorks C3500 (Fan-Fold)
|
||||
- **Stock:** 4" × 6" fan-fold paper label stock.
|
||||
- **Single-Sided:** Only the front face prints; the back section is hidden via CSS.
|
||||
- **Layout code:** `badge_4x6_fanfold`
|
||||
- **Lanyard hole:** Pre-punched 5/8" × 1/8" slot at top center — verify stock matches.
|
||||
- **First live use:** Axonius Adapt DC, June 9, 2026.
|
||||
|
||||
### Printing Workflow
|
||||
1. **Search:** Find the attendee by name or QR scan in the Badges module.
|
||||
|
||||
@@ -25,13 +25,54 @@ let lq__obj = $derived(
|
||||
|
||||
- Cold start (IDB empty) + non-blocking API writes: If you mount a component before data is written to IDB, `liveQuery` may run against an empty DB. The API write will populate IDB later, but sometimes a chain of dependent queries (e.g., presentations -> presenters) won't all rerun in the order you expect. The symptoms you described — session shows after one refresh, presenters only after a second — are consistent with either (a) queries recreated in the wrong order or (b) dependent store values being set only after some subscriptions are already created.
|
||||
|
||||
### Bootstrap Race: Account-scoped Loads Before `account_id` Is Set (2026-06)
|
||||
|
||||
Account-scoped `liveQuery` triggers can fire before `+layout.svelte`'s bootstrap Sync Effect
|
||||
has propagated the real `account_id`. Two failure modes:
|
||||
|
||||
1. **IDB empty:** fetch runs with `account_id = null`. The `localStorage` scavenge in
|
||||
`api_get_object.ts` reads the stale value from a previous session — possibly a different
|
||||
account — and caches that wrong record into IDB.
|
||||
2. **IDB has a stale record:** `liveQuery` returns a cached record from a different account as
|
||||
a valid hit, so the trigger condition (`!entry`) is never true and the correct record is
|
||||
never fetched.
|
||||
|
||||
**Rule:** Gate any trigger `$effect` that loads account-scoped data on `$slct.account_id`,
|
||||
not `$ae_loc.account_id`. `$slct` is a plain writable store (not persisted), initialized to
|
||||
`null` and set _only_ by the bootstrap Sync Effect. `$ae_loc` is a persisted store that
|
||||
hydrates from `localStorage` before effects run and may carry a stale `account_id`.
|
||||
|
||||
Also treat a non-null, non-matching `account_id` in an IDB record as a cache miss:
|
||||
|
||||
```typescript
|
||||
$effect(() => {
|
||||
const account_id = $slct.account_id; // null until bootstrap Sync Effect runs
|
||||
const api_ready = !!$ae_api?.base_url;
|
||||
const entry = $lq__obj as SomeType | null | undefined;
|
||||
|
||||
if (!browser || !account_id || !api_ready) return;
|
||||
|
||||
// null account_id on a record = global/shared fallback — still a valid hit.
|
||||
const entry_is_stale_account =
|
||||
entry !== undefined && entry !== null &&
|
||||
entry.account_id !== null &&
|
||||
entry.account_id !== account_id;
|
||||
|
||||
if (!entry || entry_is_stale_account) {
|
||||
trigger = 'load...';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
See `BOOTSTRAP__AI_Agent_Quickstart.md` → Section 7, entry 14 for the full incident writeup.
|
||||
|
||||
### Critical Discovery (2026-02-26): The "try_cache: false" Bug
|
||||
|
||||
**Symptom:** Nested data (e.g., Session → Presentations → Presenters) requires multiple manual refreshes to display on cold-start, even when using blocking loads.
|
||||
|
||||
**Root Cause:** Two interconnected issues in nested data loaders:
|
||||
1. **Disabled caching in nested loads**: Parent loads were passing `try_cache: false` to child loads, meaning presentations and presenters were fetched from API but **never written to IndexedDB**.
|
||||
2. **Missing microtask yields**: Even when caching was enabled, components would mount and subscribe to liveQuery *before* IndexedDB writes completed, causing race conditions.
|
||||
2. **Missing microtask yields**: Even when caching was enabled, components would mount and subscribe to liveQuery _before_ IndexedDB writes completed, causing race conditions.
|
||||
|
||||
**Example of the Bug:**
|
||||
```typescript
|
||||
@@ -114,14 +155,25 @@ obj.tmp_sort_3 = tmp_sort_3;
|
||||
|
||||
**Sort chain convention:** `group → priority DESC → sort ASC → [module-specific] → name`
|
||||
|
||||
**Priority encoding:** `priority ? '0' : '1'` — inverted so that `priority=true` sorts first in ascending order. This means **never use `.reverse()`** on a list sorted by `tmp_sort_*` — `.reverse()` would flip priority-true to sort last.
|
||||
**Priority encoding:** `priority ? '0' : '1'` — inverted so that `priority=true` sorts first in ascending order. This means:
|
||||
- **Dexie `.sortBy('tmp_sort_*')`** — always call without `.reverse()` before it (Dexie ignores collection-level `.reverse()` when using `.sortBy()`). If descending is needed for non-tmp_sort fields, call `.reverse()` on the resulting array after `await`.
|
||||
- **JS `.sort()` comparators** — use **ascending** `a.localeCompare(b)`, NOT `b.localeCompare(a)`. Using descending flips the priority encoding and puts `priority=false` items first.
|
||||
|
||||
```ts
|
||||
// ✅ Correct — ascending; priority=true ('0') sorts before priority=false ('1')
|
||||
list.sort((a, b) => (a.tmp_sort_1 ?? '').localeCompare(b.tmp_sort_1 ?? ''));
|
||||
|
||||
// ❌ Wrong — descending inverts the encoding; priority=false ('1') sorts first
|
||||
list.sort((a, b) => (b.tmp_sort_1 ?? '').localeCompare(a.tmp_sort_1 ?? ''));
|
||||
```
|
||||
|
||||
**Modules using `build_tmp_sort`:**
|
||||
- `ae_events__event_presentation.ts` — `tmp_sort_1/2`: group → priority → sort → start_datetime → code → name
|
||||
- `ae_events__event.ts` — `tmp_sort_1/2/3`: group → priority → sort → name → updated_on (used by IDAA recovery meetings)
|
||||
- `ae_journals__journal.ts` — `tmp_sort_1/2/3`: group → priority → sort → name → updated_on
|
||||
- `ae_journals__journal_entry.ts` — same chain as journal
|
||||
|
||||
Remaining modules (sessions, presenters, locations, posts, core) scheduled for rollout; see `TODO__Agents.md`.
|
||||
**Legacy encoding (not yet migrated to `build_tmp_sort`):** `ae_posts__post.ts`, `ae_posts__post_comment.ts`, `ae_archives__archive.ts`, `ae_archives__archive_content.ts`, `ae_sponsorships_functions.ts` use the opposite encoding (`priority ? '1' : '0'`, designed for descending sort). Their current route consumers sort by date/name so there is no visible priority bug today, but they must be migrated before any route starts sorting by `tmp_sort_*`. See `TODO__Agents.md`.
|
||||
|
||||
---
|
||||
|
||||
@@ -326,7 +378,7 @@ The `createLiveQueryStore` function creates a readable store that automatically
|
||||
|
||||
## SvelteKit Layout Hierarchy: Security and Execution Order
|
||||
|
||||
Understanding *when* SvelteKit code runs is critical for private-data modules like IDAA.
|
||||
Understanding _when_ SvelteKit code runs is critical for private-data modules like IDAA.
|
||||
|
||||
### Execution order on any navigation
|
||||
|
||||
@@ -362,7 +414,7 @@ future readers into thinking the parent gate alone is not sufficient.
|
||||
|
||||
### Where the actual pre-gate risk lives: `+page.ts` / `+layout.ts`
|
||||
|
||||
Universal load functions run *before* components mount and *before* layout effects
|
||||
Universal load functions run _before_ components mount and _before_ layout effects
|
||||
execute. They also fire during SvelteKit link prefetch — triggered by the user
|
||||
hovering a link, even if they never navigate. This makes them unsafe for private data:
|
||||
|
||||
@@ -446,7 +498,7 @@ If you must use non-blocking loads, you must pass the initial data to the compon
|
||||
|
||||
## The `untrack()` Reactive-Tracking Trap
|
||||
|
||||
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you *need* to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
|
||||
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you _need_ to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
|
||||
|
||||
### Symptom
|
||||
|
||||
@@ -510,7 +562,7 @@ Before wrapping a store read in `untrack()`, ask: **"Do I need this effect to re
|
||||
Svelte 5's `bind:` directive is more restrictive than previous versions. You can only bind to a simple **Identifier** or **MemberExpression**.
|
||||
|
||||
**❌ Invalid Pattern (Causes Compile Error):**
|
||||
Attempting to normalize a value *inside* the binding will fail.
|
||||
Attempting to normalize a value _inside_ the binding will fail.
|
||||
```svelte
|
||||
<!-- Error: Can only bind to an Identifier or MemberExpression -->
|
||||
<Launcher_menu bind:slct__event_session_id={$events_slct.event_session_id || null} />
|
||||
|
||||
@@ -114,17 +114,19 @@ corresponding `ticket_N_text` on the template provides the HTML rendered on the
|
||||
| `priority`, `sort`, `group` | int/str | Standard AE sort fields |
|
||||
| `notes` | str | Internal notes |
|
||||
|
||||
### New Field (pending backend addition)
|
||||
### Duplex / Single-Sided
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `duplex` | bool | **Planned** — when `false`, back section is hidden from print (`@media print`) |
|
||||
| `duplex` | bool | When `false`, back section is hidden from print (`@media print`) |
|
||||
|
||||
The `duplex` field controls whether the back-of-badge section renders during printing.
|
||||
When `false` (single-sided), `badge_back` gets `print:hidden` applied so only the front
|
||||
prints. The back section still displays on screen for configuration reference.
|
||||
|
||||
The first event using this system (Axonius, NYC, mid-April 2026) uses single-sided PVC
|
||||
cards on a Zebra ZC10L — `duplex` will be `false` for that event's templates.
|
||||
`duplex` is in `properties_to_save` and `show_badge_back` is derived from it in
|
||||
`ae_comp__badge_obj_view.svelte`. (Verified 2026-03-18)
|
||||
|
||||
Axonius events use `duplex = false` — single-sided printing only.
|
||||
|
||||
---
|
||||
|
||||
@@ -155,7 +157,7 @@ The print page (`print/+page.svelte`) or the badge view should conditionally add
|
||||
</svelte:head>
|
||||
```
|
||||
|
||||
This is not yet implemented — tracked as a pending Phase 1 item.
|
||||
This is implemented — `style_href` loads via `<svelte:head>` in `print/+page.svelte` and is included in `properties_to_save`. (Verified 2026-03-18)
|
||||
|
||||
### CSS Scope
|
||||
|
||||
@@ -218,7 +220,6 @@ The `properties_to_save` array in `ae_events__event_badge_template.ts` controls
|
||||
gets cached locally. Current state — fields **NOT** in properties_to_save that exist
|
||||
in DB and may be needed:
|
||||
|
||||
- `style_href` — needed once external CSS is wired via `<svelte:head>`
|
||||
- `passcode` — not needed client-side
|
||||
- `footer_title`, `footer_left`, `footer_right` — not needed (legacy)
|
||||
- `header_background`, `footer_background` — not needed (legacy)
|
||||
|
||||
@@ -35,8 +35,8 @@ Legacy patterns persisting in core logic and config modules.
|
||||
|
||||
- [ ] `src/lib/ae_sponsorships/ae_sponsorships_functions.ts`
|
||||
- [x] `src/lib/ae_core/core__hosted_files.ts` (Migrated 2026-01-20)
|
||||
- [x] `src/lib/ae_core/core__site.ts` (Migrated 2026-01-26)
|
||||
- [ ] `src/lib/ae_core/core__site_domain.ts` (STILL USES `get_ae_obj_id_crud` for bootstrap)
|
||||
- [x] `src/lib/ae_core/core__site.ts` (Migrated 2026-01-26; bootstrap path uses V3 `search_ae_obj` by `fqdn`)
|
||||
- [x] `src/lib/ae_core/core__site_domain.ts` (Retired 2026-06-02; helper removed after bootstrap migration to `core__site.ts`)
|
||||
- [ ] `src/lib/ae_core/ae_core_functions.ts` (STILL USES `get_ae_obj_id_crud` / `update_ae_obj_id_crud`)
|
||||
- [ ] `src/lib/ae_core/core__country_subdivisions.ts`
|
||||
- [ ] `src/lib/ae_core/core__time_zones.ts`
|
||||
|
||||
@@ -1,41 +1,28 @@
|
||||
# Frontend Agent Task List
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** Stable — ongoing development.
|
||||
> **Scope:** Active/open work only. Completed detail lives in archive files.
|
||||
|
||||
## 🔴 CMSC Charlotte — May 27 (Presentation Management)
|
||||
**Drive down:** May 25 | **Setup:** May 26 morning | **Show:** May 27+
|
||||
**Post-show hardening only**
|
||||
|
||||
- [x] **[Launcher] Composable open flow** — `handle_open_file()` uses `copy_from_cache_to_temp` +
|
||||
`run_osascript` / `run_cmd` directly with per-step error handling. Complete.
|
||||
- [x] **[Launcher] Slide control scripts in Svelte config** — AppleScript post_scripts live in
|
||||
`ae_launcher__default_launch_profiles.ts`. VLC focus-stealing fix applied. Complete.
|
||||
- [x] **[Launcher] Kill Apps button** — "Kill Apps" button added to Native OS config (System
|
||||
Actions, edit mode only). Kills PowerPoint, Keynote, Adobe Acrobat Reader DC, VLC, soffice.
|
||||
List overridable via `event_device.other_json.launcher.kill_process_li`. Auto-cleanup on file
|
||||
open (deferred — manual button sufficient for CMSC).
|
||||
- [ ] **[Launcher] End-to-end test on macOS** — test pptx and key opens on a real podium Mac.
|
||||
- [ ] **[Launcher/Electron] Wallpaper stops applying after several changes (post-CMSC)** —
|
||||
Append timestamp/random suffix to temp filename so macOS always sees a new path.
|
||||
- [ ] **[Launcher/Electron] Wallpaper drift after display hotplug (post-CMSC)** —
|
||||
Add resilient reconciliation loop or event-driven reapply on topology change.
|
||||
- [ ] **[Launcher/Electron] Wallpaper reliability (post-CMSC)**
|
||||
- [ ] Use timestamp/randomized temp filename so macOS always sees a new path.
|
||||
- [ ] Add resilient reconciliation loop or event-driven reapply on display topology changes.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Axonius DC — June 9 (Badge Printing)
|
||||
**Setup/Registration:** June 8 | **Show:** June 9
|
||||
|
||||
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Create/configure a fanfold badge layout
|
||||
compatible with the Epson C3500 continuous stock format.
|
||||
- [x] **[Badges] Epson C3500 fanfold badge layout** — `badge_4x6_fanfold` layout CSS created,
|
||||
wired, and documented. First live use: Axonius Adapt DC, June 9, 2026. (2026-05-15)
|
||||
|
||||
---
|
||||
|
||||
## 🚧 V3 CRUD Migration (Surgical Cleanup)
|
||||
Finalizing the 100% adoption of V3 Standard endpoints and retirement of legacy wrappers.
|
||||
|
||||
- [x] **[Badges] Presenter Agreement Form** — migrated to `update_ae_obj` (2026-05-21)
|
||||
- [ ] **[Core] Site Domain Bootstrap Refactor** — `load_ae_obj_by_fqdn__site_domain` in
|
||||
`core__site_domain.ts` still uses legacy ID-lookup-by-FQDN. Refactor to use V3
|
||||
`api.search_ae_obj` with fqdn filter per integration guide.
|
||||
- [ ] **[Core] Legacy Utility Helpers** — Refactor `ae_core_functions.ts` to use V3 helpers.
|
||||
- [ ] **[Cleanup] Delete Legacy Wrappers** — Once all callsites are migrated, remove
|
||||
`src/lib/ae_api/api_get__crud_obj_id.ts` and the legacy exports from `api.ts`.
|
||||
@@ -53,50 +40,18 @@ The app uses `svelte-persisted-store` (coarse reactivity). Migration target: rep
|
||||
- [ ] **Phase C — Remaining persisted stores:** `ae_api`, `ae_events_stores`.
|
||||
- [ ] **Phase D — Non-persisted writable stores:** `ae_sess`, `slct`, `ae_snip`, etc.
|
||||
|
||||
### [IDB Sort] `build_tmp_sort` rollout
|
||||
Shared utility in `src/lib/ae_core/core__idb_sort.ts` — fixes priority direction (inverted,
|
||||
true→'0' sorts first ASC) and zero-pads sort field (8 chars). No `.reverse()` needed.
|
||||
Sort chain: `group → priority DESC → sort ASC → [module-specific fields] → name`.
|
||||
**⚠️ Never use `.reverse()` on a `tmp_sort_*`-sorted list — inverted priority makes it wrong.**
|
||||
Documented in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` (IDB Sort section).
|
||||
### [Data Layer] IDB sorting + content version rollout
|
||||
Sorting baseline is now `build_tmp_sort` (ASC chain, no `.reverse()` on tmp-sort lists).
|
||||
|
||||
- [x] `ae_events__event_presentation` — group + priority + sort + start_datetime + code + name
|
||||
- [x] `ae_journals__journal` + `ae_journals__journal_entry` — group + priority + sort + name + updated_on
|
||||
- [ ] `ae_events__event_session` — roll out when sort behavior is reviewed
|
||||
- [ ] `ae_events__event_presenter` — roll out when sort behavior is reviewed
|
||||
- [ ] `ae_events__event_location` — roll out when sort behavior is reviewed
|
||||
- [ ] `ae_posts__post` + `ae_posts__post_comment` — note: currently use 3-char padding; align to 8
|
||||
- [ ] `ae_core__person` + `ae_core__account` — roll out when sort behavior is reviewed
|
||||
|
||||
### [Stores] IDB Content Version System
|
||||
- [x] Write `check_and_clear_idb_tables()` helper.
|
||||
- [x] Wire helper into `db_journals.ts` and IDAA layout.
|
||||
- [ ] Roll out to `db_events.ts` (module-wide: session, presenter, badge, etc.).
|
||||
- [ ] Roll out to `db_core.ts` (site_domain, person, user).
|
||||
|
||||
### [TypeScript] svelte-check hidden errors
|
||||
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.**
|
||||
Replace `children` prop binding with Svelte snippet syntax.
|
||||
- [ ] **[IDB Sort] Roll out to `ae_events__event_session`** after sort behavior review.
|
||||
- [ ] **[IDB Sort] Roll out to `ae_events__event_presenter`** after sort behavior review.
|
||||
- [ ] **[IDB Sort] Roll out to `ae_events__event_location`** after sort behavior review.
|
||||
- [ ] **[IDB Sort] Roll out to `ae_core__person` + `ae_core__account`** after sort behavior review.
|
||||
- [ ] **[IDB Version] Roll out to `db_events.ts`** (session, presenter, badge, etc.).
|
||||
- [ ] **[IDB Version] Roll out to `db_core.ts`** (site_domain, person, user).
|
||||
|
||||
### [Journals] Journal Entry Config follow-ups
|
||||
- [ ] **[Journals] Entry passcode secondary auth** — implement `passcode_hash` comparison.
|
||||
- [ ] **[Journals] Summary AI shortcut** — add button to modal.
|
||||
|
||||
---
|
||||
|
||||
### [Pres Mgmt] Sessions hide/show toggle
|
||||
- [x] **[Pres Mgmt] Hidden sessions blink on initial load** — SCENARIO 2 fallback in
|
||||
`pres_mgmt/+page.svelte` now captures `qry_hidden` as a `$derived.by` dependency and
|
||||
applies the filter in the fallback path. No blink on page load. (2026-05-28)
|
||||
- [x] **[Pres Mgmt] API call uses live store instead of snapshot** — changed
|
||||
`pres_mgmt_loc.current.qry_hidden` → `params.qry_hidden` in `handle_search_refresh`
|
||||
API call to be consistent with fast path snapshot. (2026-05-28)
|
||||
- **Note:** `hide_event_launcher` is still active — used in `menu_session_list.svelte`
|
||||
(Launcher) to CSS-hide sessions from the list. Button to toggle it is in
|
||||
`session_page_menu.svelte`. Not used in Pres Mgmt (intentional — Pres Mgmt always shows all).
|
||||
- **Note:** Non-trusted users always have `!item.hide` applied at the component level
|
||||
in `ae_comp__event_session_obj_li.svelte` regardless of `qry_hidden`. Toggle is
|
||||
trusted-access-only in practice; direct session links still work for non-trusted users.
|
||||
|
||||
---
|
||||
|
||||
@@ -128,3 +83,4 @@ See the full completed history in:
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-03.md](documentation/archive/TODO__Agents__ARCHIVE_2026-03.md)
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-04.md](documentation/archive/TODO__Agents__ARCHIVE_2026-04.md)
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-05.md](documentation/archive/TODO__Agents__ARCHIVE_2026-05.md)
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-06.md](documentation/archive/TODO__Agents__ARCHIVE_2026-06.md)
|
||||
|
||||
139
documentation/archive/TODO__Agents__ARCHIVE_2026-06.md
Normal file
139
documentation/archive/TODO__Agents__ARCHIVE_2026-06.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Frontend Agent Task List
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** Stable — ongoing development.
|
||||
|
||||
## 🔴 CMSC Charlotte — May 27 (Presentation Management)
|
||||
**Drive down:** May 25 | **Setup:** May 26 morning | **Show:** May 27+
|
||||
|
||||
- [x] **[Launcher] Composable open flow** — `handle_open_file()` uses `copy_from_cache_to_temp` +
|
||||
`run_osascript` / `run_cmd` directly with per-step error handling. Complete.
|
||||
- [x] **[Launcher] Slide control scripts in Svelte config** — AppleScript post_scripts live in
|
||||
`ae_launcher__default_launch_profiles.ts`. VLC focus-stealing fix applied. Complete.
|
||||
- [x] **[Launcher] Kill Apps button** — "Kill Apps" button added to Native OS config (System
|
||||
Actions, edit mode only). Kills PowerPoint, Keynote, Adobe Acrobat Reader DC, VLC, soffice.
|
||||
List overridable via `event_device.other_json.launcher.kill_process_li`. Auto-cleanup on file
|
||||
open (deferred — manual button sufficient for CMSC).
|
||||
- [x] **[Launcher] Hidden/deleted files still visible in Presenter file list** — Fixed by
|
||||
API-to-Dexie stale-record pruning plus Launcher background refresh loops for file lists.
|
||||
`ae_events__event_file.ts` now prunes stale records after refresh, and
|
||||
`launcher_background_sync.svelte` refreshes/prunes selected session and presenter file lists.
|
||||
(`fix(launcher): refresh file lists periodically to prune deleted/hidden files`, 2026-05)
|
||||
- [ ] **[Launcher/Electron] Wallpaper stops applying after several changes (post-CMSC)** —
|
||||
Append timestamp/random suffix to temp filename so macOS always sees a new path.
|
||||
- [ ] **[Launcher/Electron] Wallpaper drift after display hotplug (post-CMSC)** —
|
||||
Add resilient reconciliation loop or event-driven reapply on topology change.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Axonius DC — June 9 (Badge Printing)
|
||||
**Setup/Registration:** June 8 | **Show:** June 9
|
||||
|
||||
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Create/configure a fanfold badge layout
|
||||
compatible with the Epson C3500 continuous stock format.
|
||||
|
||||
---
|
||||
|
||||
## 🚧 V3 CRUD Migration (Surgical Cleanup)
|
||||
Finalizing the 100% adoption of V3 Standard endpoints and retirement of legacy wrappers.
|
||||
|
||||
- [x] **[Badges] Presenter Agreement Form** — migrated to `update_ae_obj` (2026-05-21)
|
||||
- [x] **[Core] Site Domain Bootstrap Refactor** — Bootstrap path is already on V3 in
|
||||
`ae_core__site.ts` via `lookup_site_domain()` using `api.search_ae_obj` with FQDN filter
|
||||
(used by `src/routes/+layout.ts`).
|
||||
Follow-up cleanup complete: retired legacy helper `core__site_domain.ts`. (2026-06-02)
|
||||
- [ ] **[Core] Legacy Utility Helpers** — Refactor `ae_core_functions.ts` to use V3 helpers.
|
||||
- [ ] **[Cleanup] Delete Legacy Wrappers** — Once all callsites are migrated, remove
|
||||
`src/lib/ae_api/api_get__crud_obj_id.ts` and the legacy exports from `api.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 🚧 High Priority Workstreams
|
||||
|
||||
### [Stores] Svelte 4 → Svelte 5 State Migration
|
||||
The app uses `svelte-persisted-store` (coarse reactivity). Migration target: replace with Svelte 5
|
||||
`$state`-based persistence for fine-grained updates.
|
||||
|
||||
- [ ] **Phase A — Project plan + wrapper decision:** Write `PROJECT__Stores_Svelte5_Migration.md`.
|
||||
- [ ] **Phase B — Core auth stores (highest impact):** `ae_loc`, `idaa_loc`.
|
||||
- [ ] **Phase C — Remaining persisted stores:** `ae_api`, `ae_events_stores`.
|
||||
- [ ] **Phase D — Non-persisted writable stores:** `ae_sess`, `slct`, `ae_snip`, etc.
|
||||
|
||||
### [IDB Sort] `build_tmp_sort` rollout
|
||||
Shared utility in `src/lib/ae_core/core__idb_sort.ts` — fixes priority direction (inverted,
|
||||
true→'0' sorts first ASC) and zero-pads sort field (8 chars). No `.reverse()` needed.
|
||||
Sort chain: `group → priority DESC → sort ASC → [module-specific fields] → name`.
|
||||
**⚠️ Never use `.reverse()` on a `tmp_sort_*`-sorted list — inverted priority makes it wrong.**
|
||||
Documented in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` (IDB Sort section).
|
||||
|
||||
- [x] `ae_events__event_presentation` — group + priority + sort + start_datetime + code + name
|
||||
- [x] `ae_journals__journal` + `ae_journals__journal_entry` — group + priority + sort + name + updated_on
|
||||
- [ ] `ae_events__event_session` — roll out when sort behavior is reviewed
|
||||
- [ ] `ae_events__event_presenter` — roll out when sort behavior is reviewed
|
||||
- [ ] `ae_events__event_location` — roll out when sort behavior is reviewed
|
||||
- [x] `ae_posts__post` + `ae_posts__post_comment` — migrated to `build_tmp_sort` with 8-char padding; BB comment list consumer updated for ASC tmp_sort ordering. (2026-06-02)
|
||||
- [ ] `ae_core__person` + `ae_core__account` — roll out when sort behavior is reviewed
|
||||
|
||||
### [Stores] IDB Content Version System
|
||||
- [x] Write `check_and_clear_idb_tables()` helper.
|
||||
- [x] Wire helper into `db_journals.ts` and IDAA layout.
|
||||
- [ ] Roll out to `db_events.ts` (module-wide: session, presenter, badge, etc.).
|
||||
- [ ] Roll out to `db_core.ts` (site_domain, person, user).
|
||||
|
||||
### [TypeScript] svelte-check hidden errors
|
||||
- [x] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.**
|
||||
Verified no remaining `children={...}` bindings on `<Modal>` and `npx svelte-check` is clean. (2026-06-02)
|
||||
|
||||
### [Journals] Journal Entry Config follow-ups
|
||||
- [ ] **[Journals] Entry passcode secondary auth** — implement `passcode_hash` comparison.
|
||||
- [x] **[Journals] Summary AI shortcut** — added Quick Actions button in entry config modal and wired it to close modal + scroll to AI tools panel in entry edit view. (2026-06-02)
|
||||
|
||||
### [Cleanup] Migrate remaining `lucide-svelte` imports to `@lucide/svelte`
|
||||
- [x] **[Cleanup] Migrate remaining `lucide-svelte` imports to `@lucide/svelte`**
|
||||
Migrated all 5 listed files to `@lucide/svelte` and uninstalled `lucide-svelte` from dependencies. (2026-06-02)
|
||||
|
||||
---
|
||||
|
||||
### [Pres Mgmt] Sessions hide/show toggle
|
||||
- [x] **[Pres Mgmt] Hidden sessions blink on initial load** — SCENARIO 2 fallback in
|
||||
`pres_mgmt/+page.svelte` now captures `qry_hidden` as a `$derived.by` dependency and
|
||||
applies the filter in the fallback path. No blink on page load. (2026-05-28)
|
||||
- [x] **[Pres Mgmt] API call uses live store instead of snapshot** — changed
|
||||
`pres_mgmt_loc.current.qry_hidden` → `params.qry_hidden` in `handle_search_refresh`
|
||||
API call to be consistent with fast path snapshot. (2026-05-28)
|
||||
- **Note:** `hide_event_launcher` is still active — used in `menu_session_list.svelte`
|
||||
(Launcher) to CSS-hide sessions from the list. Button to toggle it is in
|
||||
`session_page_menu.svelte`. Not used in Pres Mgmt (intentional — Pres Mgmt always shows all).
|
||||
- **Note:** Non-trusted users always have `!item.hide` applied at the component level
|
||||
in `ae_comp__event_session_obj_li.svelte` regardless of `qry_hidden`. Toggle is
|
||||
trusted-access-only in practice; direct session links still work for non-trusted users.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing & Optimization
|
||||
|
||||
- [ ] **[IDAA] IDB fast-path contact search** — parse `contact_li_json` in `search__event()`.
|
||||
- [ ] **[IDAA] Optimize Recovery Meetings SQL VIEW and indexes.**
|
||||
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage** in all other event search pages.
|
||||
- [ ] **[Launcher/VLC] Linux playback investigation** — fullscreen + pause-on-end flags.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ DevOps & Backend
|
||||
|
||||
- [ ] **[Backend] `event_file` — add `cfg_json` column (post-CMSC)** — The per-file display
|
||||
override currently uses a localStorage workaround (`$events_loc.launcher.file_display_overrides`)
|
||||
because `event_file` has no JSON blob column. Proper fix: add `cfg_json` to the `event_file` DB
|
||||
table, expose it through the FastAPI model, then migrate the frontend back to reading/writing the
|
||||
backend field (restoring global/cross-device persistence). Frontend code is in
|
||||
`launcher_file_cont.svelte` — search for `file_display_overrides`.
|
||||
- [ ] **[Backend] Re-add `Access-Control-Allow-Private-Network: true` CORS header.**
|
||||
- [ ] **[DevOps] Nginx caching** — Investigate `index.html` cache-pickup issues.
|
||||
- [ ] **[DevOps] Simplify Dockerfile env file selection** — Use plain `.env` instead of `BUILD_MODE`.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completed (archived)
|
||||
See the full completed history in:
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-03.md](documentation/archive/TODO__Agents__ARCHIVE_2026-03.md)
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-04.md](documentation/archive/TODO__Agents__ARCHIVE_2026-04.md)
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-05.md](documentation/archive/TODO__Agents__ARCHIVE_2026-05.md)
|
||||
273
package-lock.json
generated
273
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "osit-aether-app-svelte",
|
||||
"version": "3.00.10",
|
||||
"version": "3.00.20",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "osit-aether-app-svelte",
|
||||
"version": "3.00.10",
|
||||
"version": "3.00.20",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.20.0",
|
||||
"@codemirror/commands": "^6.10.0",
|
||||
@@ -26,12 +26,10 @@
|
||||
"@lucide/svelte": "^0.*.0",
|
||||
"@popperjs/core": "^2.11.0",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"axios": "^1.7.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"dexie": "^4.0.0",
|
||||
"flowbite-svelte": "^1.28.1",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"lucide-svelte": "^0.*.0",
|
||||
"marked": "^17.0.0",
|
||||
"openai": "^6.10.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
@@ -3929,23 +3927,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
@@ -3976,19 +3957,6 @@
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -4101,18 +4069,6 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
@@ -4240,15 +4196,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@@ -4299,20 +4246,6 @@
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -4332,24 +4265,6 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
@@ -4357,33 +4272,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||
@@ -4935,42 +4823,6 @@
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
@@ -5003,43 +4855,6 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -5065,18 +4880,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -5092,33 +4895,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
@@ -5654,15 +5430,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-svelte": {
|
||||
"version": "0.577.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.577.0.tgz",
|
||||
"integrity": "sha512-0i88o57KsaHWnc80J57fY99CWzlZsSdtH5kKjLUJa7z8dum/9/AbINNLzJ7NiRFUdOgMnfAmJt8jFbW2zeC5qQ==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"svelte": "^3 || ^4 || ^5.0.0-next.42"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
@@ -5693,36 +5460,6 @@
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
@@ -6317,12 +6054,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
||||
@@ -105,12 +105,10 @@
|
||||
"@lucide/svelte": "^0.*.0",
|
||||
"@popperjs/core": "^2.11.0",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"axios": "^1.7.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"dexie": "^4.0.0",
|
||||
"flowbite-svelte": "^1.28.1",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"lucide-svelte": "^0.*.0",
|
||||
"marked": "^17.0.0",
|
||||
"openai": "^6.10.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte specific
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
import * as Lucide from '@lucide/svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
|
||||
// Imports
|
||||
// Import components and elements
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
import * as Lucide from '@lucide/svelte';
|
||||
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
|
||||
|
||||
// Import storage, functions, and libraries
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
export interface Site_Domain {
|
||||
id: string;
|
||||
// id_random: string;
|
||||
site_id: string;
|
||||
site_id_random?: string;
|
||||
|
||||
fqdn: string;
|
||||
access_key?: null | string;
|
||||
required_referrer?: null | string;
|
||||
valid_for?: null | number; // In hours
|
||||
|
||||
enable: null | boolean;
|
||||
hide?: null | boolean;
|
||||
priority?: null | boolean;
|
||||
sort?: null | number;
|
||||
group?: null | string;
|
||||
notes?: null | string;
|
||||
created_on: Date;
|
||||
updated_on?: null | Date;
|
||||
}
|
||||
|
||||
import { api } from '$lib/api/api';
|
||||
|
||||
/**
|
||||
* Fetches a site_domain object by its Fully Qualified Domain Name (FQDN).
|
||||
*
|
||||
* @param api_cfg - The API configuration object.
|
||||
* @param fqdn - The FQDN of the site domain to fetch.
|
||||
* @param timeout - The request timeout in milliseconds.
|
||||
* @param log_lvl - The logging level.
|
||||
* @returns The site domain object or null if not found.
|
||||
*/
|
||||
export async function load_ae_obj_by_fqdn__site_domain({
|
||||
api_cfg,
|
||||
fqdn,
|
||||
timeout = 7000,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
fqdn: string;
|
||||
timeout?: number;
|
||||
log_lvl?: number;
|
||||
}): Promise<any> {
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
`*** load_ae_obj_by_fqdn__site_domain() *** api.base_url=${api_cfg.base_url}, fqdn=${fqdn}, timeout=${timeout}`
|
||||
);
|
||||
}
|
||||
|
||||
const params = {};
|
||||
|
||||
try {
|
||||
const site_domain_obj = await api.get_ae_obj_id_crud({
|
||||
api_cfg: api_cfg,
|
||||
no_account_id: true, // This seems to be a special case for this endpoint
|
||||
obj_type: 'site_domain',
|
||||
obj_id: fqdn, // NOTE: This is the FQDN, not the ID.
|
||||
use_alt_table: true,
|
||||
use_alt_base: true,
|
||||
params: params,
|
||||
timeout: timeout,
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
|
||||
if (site_domain_obj) {
|
||||
return site_domain_obj;
|
||||
} else {
|
||||
console.log('No results returned.');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('No results returned or failed.', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -75,8 +75,10 @@ export function journal_entry_matches_search(
|
||||
}
|
||||
|
||||
export function journal_entry_compare_for_list(a: any, b: any): number {
|
||||
// tmp_sort_1 is built by build_tmp_sort() for ascending comparison:
|
||||
// priority=true encodes as '0', priority=false as '1', so ASC puts priority first.
|
||||
return (
|
||||
(b?.tmp_sort_1 ?? '').localeCompare(a?.tmp_sort_1 ?? '') ||
|
||||
(a?.tmp_sort_1 ?? '').localeCompare(b?.tmp_sort_1 ?? '') ||
|
||||
(b?.updated_on ?? '').localeCompare(a?.updated_on ?? '') ||
|
||||
(b?.journal_entry_id ?? '').localeCompare(a?.journal_entry_id ?? '')
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { api } from '$lib/api/api';
|
||||
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||
|
||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||
import { db_posts } from '$lib/ae_posts/db_posts';
|
||||
@@ -570,15 +571,16 @@ export async function process_ae_obj__post_props({
|
||||
if (!obj.account_id_random) obj.account_id_random = account_id;
|
||||
}
|
||||
obj.name = obj.title;
|
||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
||||
const updated =
|
||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${updated}`;
|
||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${obj.updated_on ?? ''}_${obj.created_on ?? ''}`;
|
||||
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||
prefix: [obj.group ?? ''],
|
||||
priority: obj.priority,
|
||||
sort: obj.sort,
|
||||
fields_1: [obj.updated_on ?? obj.created_on ?? ''],
|
||||
fields_2: [obj.updated_on ?? '', obj.created_on ?? ''],
|
||||
pad_width: 8
|
||||
});
|
||||
obj.tmp_sort_1 = tmp_sort_1;
|
||||
obj.tmp_sort_2 = tmp_sort_2;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { api } from '$lib/api/api';
|
||||
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||
|
||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||
import { db_posts } from '$lib/ae_posts/db_posts';
|
||||
@@ -383,15 +384,16 @@ export async function process_ae_obj__post_comment_props({
|
||||
obj_type: 'post_comment',
|
||||
log_lvl,
|
||||
specific_processor: (obj) => {
|
||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
||||
const updated =
|
||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${updated}`;
|
||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${obj.updated_on ?? ''}_${obj.created_on ?? ''}`;
|
||||
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||
prefix: [obj.group ?? ''],
|
||||
priority: obj.priority,
|
||||
sort: obj.sort,
|
||||
fields_1: [obj.updated_on ?? obj.created_on ?? ''],
|
||||
fields_2: [obj.updated_on ?? '', obj.created_on ?? ''],
|
||||
pad_width: 8
|
||||
});
|
||||
obj.tmp_sort_1 = tmp_sort_1;
|
||||
obj.tmp_sort_2 = tmp_sort_2;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
import * as Lucide from '@lucide/svelte';
|
||||
|
||||
/**
|
||||
* Returns a Lucide icon component based on the provided file extension.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -165,9 +165,25 @@ $effect(() => {
|
||||
$effect(() => {
|
||||
const account_id = $slct.account_id;
|
||||
const api_ready = !!$ae_api?.base_url;
|
||||
const entry = $lq__ds_obj;
|
||||
const entry = $lq__ds_obj as ae_DataStore | null | undefined;
|
||||
|
||||
if (browser && api_ready && !entry && ds_loading_status === 'starting') {
|
||||
// Don't fire until the bootstrap Sync Effect has set a real account_id.
|
||||
// Without this guard, the fetch runs with null account_id and the
|
||||
// localStorage scavenge in get_object picks up a stale account_id from a
|
||||
// previous session, returning the wrong account's record.
|
||||
if (!browser || !account_id || !api_ready || ds_loading_status !== 'starting') return;
|
||||
|
||||
// Also reload when IDB has a record but it belongs to a different account
|
||||
// (not null/global and not the current account). This handles the case where
|
||||
// a previous dev/demo session left account-specific rows in IDB that score
|
||||
// as the "best" liveQuery match even though they are for the wrong account.
|
||||
const entry_is_stale_account =
|
||||
entry !== undefined &&
|
||||
entry !== null &&
|
||||
entry.account_id !== null &&
|
||||
entry.account_id !== account_id;
|
||||
|
||||
if (!entry || entry_is_stale_account) {
|
||||
trigger = 'load__ds__code';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ The Aether system is built on a foundation of several core modules. The UI compo
|
||||
### Sites & Site Domains
|
||||
|
||||
- **Description:** Manages different sites or instances of the application and their associated domain names. This allows for multi-tenant configurations.
|
||||
- **Logic:** `src/lib/ae_core/core__site.ts` and `core__site_domain.ts`
|
||||
- **Logic:** `src/lib/ae_core/core__site.ts`
|
||||
|
||||
### Users
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ function clear_sess() {
|
||||
container m-auto flex h-full min-h-full
|
||||
w-full max-w-7xl
|
||||
min-w-full flex-col gap-1
|
||||
overflow-auto
|
||||
overflow-auto [scrollbar-gutter:stable]
|
||||
|
||||
bg-gray-50 text-gray-800
|
||||
dark:bg-gray-900 dark:text-gray-200
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
interface Props {
|
||||
/** @type {import('./$types').PageData} */
|
||||
data: any;
|
||||
@@ -31,7 +32,7 @@ import Comp_badge_obj_li from './ae_comp__badge_obj_li.svelte';
|
||||
import Comp_badge_create_form from './ae_comp__badge_create_form.svelte';
|
||||
import Comp_badge_upload_form from './ae_comp__badge_upload_form.svelte';
|
||||
|
||||
import { LoaderCircle, UserPlus, Printer, Upload, FileText, BarChart2 } from '@lucide/svelte';
|
||||
import { UserPlus, Printer, Upload, FileText, ChartColumnBig } from '@lucide/svelte';
|
||||
|
||||
// Load templates for this event so the create form can show the selector and
|
||||
// derive badge_type_code_li from whichever template the user picks.
|
||||
@@ -472,7 +473,7 @@ async function handle_search_refresh(params: any) {
|
||||
></Comp_badge_search>
|
||||
|
||||
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}
|
||||
<div class="flex flex-row gap-1 items-center justify-center">
|
||||
<div transition:slide={{ duration: 200 }} class="flex flex-row gap-1 items-center justify-center">
|
||||
{#if badges_loc.current.enable_add_badge_btn ?? true}
|
||||
<button
|
||||
type="button"
|
||||
@@ -523,7 +524,7 @@ async function handle_search_refresh(params: any) {
|
||||
<a
|
||||
href={`/events/${$events_slct?.event_id}/badges/stats`}
|
||||
class="btn btn-tertiary">
|
||||
<BarChart2 size="1em" /> Badge Printing Stats
|
||||
<ChartColumnBig size="1em" /> Badge Printing Stats
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -591,15 +592,10 @@ async function handle_search_refresh(params: any) {
|
||||
{/if}
|
||||
</dialog>
|
||||
|
||||
{#if $events_sess?.badges?.search_status === 'loading' && event_badge_id_li.length === 0}
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-10 text-center opacity-50">
|
||||
<LoaderCircle size="3em" class="mx-auto mb-4 animate-spin" />
|
||||
<p class="text-xl">Loading badges...</p>
|
||||
</div>
|
||||
{:else}
|
||||
<Comp_badge_obj_li {lq__event_badge_obj_li} log_lvl={1}></Comp_badge_obj_li>
|
||||
{/if}
|
||||
<Comp_badge_obj_li
|
||||
{lq__event_badge_obj_li}
|
||||
search_status={$events_sess?.badges?.search_status ?? null}
|
||||
log_lvl={1} />
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
interface Props {
|
||||
container_class_li?: string | Array<string>;
|
||||
lq__event_badge_obj_li: any;
|
||||
search_status?: string | null;
|
||||
log_lvl?: number;
|
||||
show_sensitive_fields?: boolean;
|
||||
hide_affiliations?: boolean;
|
||||
@@ -12,6 +13,7 @@ interface Props {
|
||||
let {
|
||||
container_class_li = [],
|
||||
lq__event_badge_obj_li,
|
||||
search_status = null,
|
||||
log_lvl = $bindable(0),
|
||||
show_sensitive_fields = true,
|
||||
hide_affiliations = false,
|
||||
@@ -27,14 +29,14 @@ import {
|
||||
Check,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileSearch,
|
||||
Link,
|
||||
LoaderCircle,
|
||||
Mail,
|
||||
MapPin,
|
||||
Printer,
|
||||
Tags,
|
||||
User
|
||||
User,
|
||||
UserSearch
|
||||
} from '@lucide/svelte';
|
||||
// Track per-badge copy state for the "Review Link" clipboard button
|
||||
let copy_status: Record<string, 'idle' | 'copied'> = $state({});
|
||||
@@ -42,6 +44,7 @@ let copy_status: Record<string, 'idle' | 'copied'> = $state({});
|
||||
// Access level shortcuts
|
||||
let is_trusted = $derived($ae_loc.trusted_access === true);
|
||||
let is_admin = $derived($ae_loc.administrator_access === true);
|
||||
let is_manager = $derived($ae_loc.manager_access === true);
|
||||
let is_public = $derived($ae_loc.public_access === true); // public passcode or higher — may print first prints
|
||||
let is_edit_mode = $derived($ae_loc.edit_mode === true);
|
||||
|
||||
@@ -113,12 +116,24 @@ let visible_badge_obj_li = $derived(
|
||||
if (ps === 'not_printed') return (item.print_count ?? 0) < 1 && hide_ok;
|
||||
return hide_ok; // 'all'
|
||||
}
|
||||
// Public (kiosk) / authenticated / anonymous: only unprinted, never hidden.
|
||||
// Badge kiosks run at public_access — attendees should only see their own
|
||||
// unprinted badge, never a list of already-printed ones.
|
||||
// Public/Attendee (public_access, not trusted): show all non-hidden.
|
||||
// Printed badges appear read-only so attendees can see their check-in status.
|
||||
if (is_public) return !item.hide;
|
||||
// Below public (anonymous, authenticated): only unprinted, never hidden.
|
||||
return (item.print_count ?? 0) < 1 && !item.hide;
|
||||
});
|
||||
|
||||
// For public (attendee) kiosk: sort unprinted first so attendees find their
|
||||
// own badge at the top; already-checked-in records fall to the bottom.
|
||||
if (is_public && !is_trusted && filtered.length > 1) {
|
||||
filtered.sort((a: any, b: any) => {
|
||||
const a_p = (a.print_count ?? 0) > 0;
|
||||
const b_p = (b.print_count ?? 0) > 0;
|
||||
if (a_p !== b_p) return a_p ? 1 : -1;
|
||||
return 0; // preserve existing name order within each group
|
||||
});
|
||||
}
|
||||
|
||||
if (log_lvl)
|
||||
console.log(
|
||||
`visible_badge_obj_li: Input=${list.length}, Output=${filtered.length}`
|
||||
@@ -154,9 +169,13 @@ let visible_badge_obj_li = $derived(
|
||||
event_badge_obj?.full_name_override ??
|
||||
event_badge_obj?.full_name ??
|
||||
`${event_badge_obj?.given_name ?? ''} ${event_badge_obj?.family_name ?? ''}`.trim()}
|
||||
{@const can_print = (is_public && !is_printed) || (is_trusted && is_edit_mode)}
|
||||
{@const is_attendee_view = is_public && !is_trusted}
|
||||
{@const is_attendee_printed = is_attendee_view && is_printed}
|
||||
{@const can_print = is_trusted && (!is_printed || is_edit_mode)}
|
||||
{@const row_clickable = can_print || (is_attendee_view && !is_printed)}
|
||||
{@const print_href = `/events/${event_badge_obj?.event_id}/badges/${event_badge_obj?.event_badge_id}/print`}
|
||||
{@const review_href = build_review_url(event_badge_obj)}
|
||||
{@const row_href = is_trusted ? print_href : review_href}
|
||||
|
||||
<li
|
||||
class="
|
||||
@@ -169,18 +188,13 @@ let visible_badge_obj_li = $derived(
|
||||
class="flex w-full flex-row flex-wrap items-center justify-between gap-2">
|
||||
<!-- Left cluster: main action (name + email + affiliations) -->
|
||||
<div class="flex min-w-0 grow flex-row flex-wrap items-center gap-x-3 gap-y-1">
|
||||
{#if can_print}
|
||||
{#if row_clickable}
|
||||
<a
|
||||
href={print_href}
|
||||
href={row_href}
|
||||
class="hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center gap-3 justify-start px-3 py-2 text-left text-lg font-bold flex-1 transition-colors duration-1000 hover:duration-300 min-w-0 preset-tonal-primary rounded-lg"
|
||||
title="
|
||||
Print badge for
|
||||
{display_name}
|
||||
email: <{is_trusted ? event_badge_obj?.email : obscure_email(event_badge_obj?.email)}>
|
||||
{event_badge_obj?.affiliations_override ?? event_badge_obj?.affiliations}
|
||||
type: {event_badge_obj?.badge_type}
|
||||
id: {event_badge_obj.event_badge_id}
|
||||
"
|
||||
title={is_trusted
|
||||
? `Print badge for ${display_name} · ${event_badge_obj?.affiliations_override ?? event_badge_obj?.affiliations ?? ''} · ${event_badge_obj?.badge_type ?? ''} · id: ${event_badge_obj.event_badge_id}`
|
||||
: `Review your badge info`}
|
||||
>
|
||||
{#if event_badge_obj?.hide}
|
||||
<EyeOff size="1.1em" class="text-gray-400" />
|
||||
@@ -208,6 +222,23 @@ let visible_badge_obj_li = $derived(
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
{:else if is_attendee_printed}
|
||||
<!-- Public attendee, badge already printed: show check-in status, no action -->
|
||||
<div class="flex items-center gap-3 justify-start px-3 py-2 flex-1 min-w-0 rounded-lg bg-success-500/10">
|
||||
<Check size="1.1em" class="text-success-600 dark:text-success-400 shrink-0" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="truncate font-semibold">{display_name}</span>
|
||||
<span class="text-xs text-success-700 dark:text-success-300 opacity-80">
|
||||
Checked in · {print_count}×
|
||||
{#if event_badge_obj.print_first_datetime}
|
||||
· First: {ae_util.iso_datetime_formatter(event_badge_obj.print_first_datetime, 'datetime_iso_12_no_seconds')}
|
||||
{/if}
|
||||
{#if event_badge_obj.print_last_datetime}
|
||||
· Last: {ae_util.iso_datetime_formatter(event_badge_obj.print_last_datetime, 'datetime_iso_12_no_seconds')}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center gap-3 justify-start px-3 py-2 text-left text-lg font-bold flex-1 transition-colors duration-1000 hover:duration-300 min-w-0 preset-tonal-primary rounded-lg cursor-not-allowed"
|
||||
@@ -256,8 +287,8 @@ let visible_badge_obj_li = $derived(
|
||||
|
||||
<!-- Right: up to 4 action buttons -->
|
||||
<div class="flex shrink-0 flex-row items-center gap-1">
|
||||
<!-- 1. Print Badge: Public+ first print; Trusted + Edit Mode for reprint -->
|
||||
{#if (is_public && !is_printed) || (is_trusted && is_edit_mode)}
|
||||
<!-- 1. Print Badge: Trusted — first print anytime, reprint in Edit Mode or Manager+ -->
|
||||
{#if is_trusted && (!is_printed || is_edit_mode || is_manager)}
|
||||
<a
|
||||
href={`/events/${event_badge_obj?.event_id}/badges/${event_badge_obj?.event_badge_id}/print`}
|
||||
class="hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center gap-1 px-3 py-2 text-base font-bold transition-colors duration-1000 hover:duration-300 min-w-0 preset-tonal-primary rounded-lg border border-primary-200-800
|
||||
@@ -312,9 +343,8 @@ let visible_badge_obj_li = $derived(
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- 4. Email Review Link: Administrator + Edit Mode only
|
||||
Temporarily restricted — TODO: restore broader access after Axonius 2026 -->
|
||||
{#if is_admin && is_edit_mode}
|
||||
<!-- 4. Email Review Link: Manager+ always; Administrator + Edit Mode otherwise -->
|
||||
{#if is_admin && (is_edit_mode || is_manager)}
|
||||
<button
|
||||
type="button"
|
||||
class="hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center gap-1 px-3 py-2 text-base font-bold transition-colors duration-1000 hover:duration-300 min-w-0 preset-tonal-primary rounded-lg border border-primary-200-800"
|
||||
@@ -380,12 +410,17 @@ let visible_badge_obj_li = $derived(
|
||||
</ul>
|
||||
{:else}
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-20 text-center opacity-50">
|
||||
<FileSearch size="3em" class="mx-auto mb-2 opacity-20" />
|
||||
<p>
|
||||
No badges found matching your criteria. Try adjusting your
|
||||
filters.
|
||||
</p>
|
||||
class="flex w-full flex-col items-center justify-center p-16 text-center opacity-50">
|
||||
{#if search_status === 'loading'}
|
||||
<LoaderCircle size="3em" class="mx-auto mb-2 animate-spin" />
|
||||
<p class="text-xl">Searching...</p>
|
||||
{:else if !is_trusted && !(badges_loc.current.fulltext_search_qry_str ?? '').trim()}
|
||||
<UserSearch size="3em" class="mx-auto mb-2 opacity-20" />
|
||||
<p class="text-xl">Search by name, email, or organization above to find your badge.</p>
|
||||
{:else}
|
||||
<UserSearch size="3em" class="mx-auto mb-2 opacity-20" />
|
||||
<p class="text-xl">No badges found matching your criteria.</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
StepForward
|
||||
|
||||
} from '@lucide/svelte';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import { events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
|
||||
@@ -55,7 +56,10 @@ let effective_min_chars = $derived.by(() => {
|
||||
|
||||
function handle_search_trigger() {
|
||||
const qry = (badges_loc.current.fulltext_search_qry_str ?? '').trim();
|
||||
if (qry.length < effective_min_chars) return;
|
||||
if (qry.length < effective_min_chars) {
|
||||
document.getElementById('badge_fulltext_search_qry_str')?.focus();
|
||||
return;
|
||||
}
|
||||
badges_loc.current.search_version++;
|
||||
}
|
||||
|
||||
@@ -87,6 +91,7 @@ function handle_qr_scan_result(event: {
|
||||
class="ae_group filters_and_search flex w-full flex-col items-center justify-center gap-2">
|
||||
{#if $events_sess.badges.show_form__search}
|
||||
<form
|
||||
transition:fade={{ duration: 200 }}
|
||||
onsubmit={prevent_default(() => {
|
||||
handle_search_trigger();
|
||||
})}
|
||||
@@ -95,7 +100,7 @@ function handle_qr_scan_result(event: {
|
||||
<div
|
||||
class="flex grow flex-col xl:flex-row flex-wrap items-center justify-center gap-2">
|
||||
{#if ($ae_loc.trusted_access && $ae_loc.edit_mode) || $ae_loc.manager_access}
|
||||
<div class="flex flex-row flex-wrap items-center justify-center gap-1">
|
||||
<div transition:slide={{ duration: 200 }} class="flex flex-row flex-wrap items-center justify-center gap-1">
|
||||
<span class="flex flex-row flex-wrap items-center justify-center gap-1">
|
||||
<select
|
||||
bind:value={badges_loc.current.search_badge_type_code}
|
||||
@@ -159,6 +164,7 @@ function handle_qr_scan_result(event: {
|
||||
|
||||
{#if (badges_loc.current.enable_search_qr && $ae_loc.edit_mode)}
|
||||
<button
|
||||
transition:fade={{ duration: 150 }}
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$events_sess.badges.show_form__search = false;
|
||||
@@ -177,12 +183,12 @@ function handle_qr_scan_result(event: {
|
||||
onclick={() => document.getElementById('badge_fulltext_search_qry_str')?.focus()}
|
||||
aria-label="Start here — focus search field"
|
||||
data-testid="badge-start-btn"
|
||||
class="btn btn-sm preset-filled-secondary-200-800 font-semibold"
|
||||
class="btn btn-sm preset-filled-secondary-200-800 font-semibold transition-opacity duration-300"
|
||||
class:opacity-30={!!badges_loc.current.fulltext_search_qry_str}
|
||||
class:hidden={$ae_loc.trusted_access}
|
||||
>
|
||||
<StepForward size="1em" class="mx-1" />
|
||||
Start Here
|
||||
Start Here:
|
||||
</button>
|
||||
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
@@ -203,10 +209,11 @@ function handle_qr_scan_result(event: {
|
||||
bg-red-50-900 dark:bg-red-50-950
|
||||
focus:bg-white
|
||||
focus:text-black
|
||||
border border-l-2 border-b-2
|
||||
border-l-primary-500 border-b-primary-500
|
||||
border-r-primary-300 border-t-primary-300
|
||||
dark:border-l-primary-800 dark:border-b-primary-800
|
||||
border border-l-2 border-r-2 border-b-3
|
||||
border-l-success-300 border-b-success-500
|
||||
border-r-success-300 border-t-success-300
|
||||
dark:border-l-success-800 dark:border-b-success-800
|
||||
preset-tonal-success
|
||||
"
|
||||
onkeyup={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
@@ -218,17 +225,19 @@ function handle_qr_scan_result(event: {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if (badges_loc.current.fulltext_search_qry_str ?? '').trim().length < effective_min_chars}
|
||||
<p class="w-full text-center text-xs opacity-50">
|
||||
Enter at least {effective_min_chars} character{effective_min_chars === 1 ? '' : 's'} to search
|
||||
</p>
|
||||
{/if}
|
||||
<p class="w-full text-center text-xs transition-opacity duration-200"
|
||||
class:opacity-0={(badges_loc.current.fulltext_search_qry_str ?? '').trim().length >= effective_min_chars}
|
||||
class:opacity-50={(badges_loc.current.fulltext_search_qry_str ?? '').trim().length < effective_min_chars}>
|
||||
Enter at least {effective_min_chars} character{effective_min_chars === 1 ? '' : 's'} to search
|
||||
</p>
|
||||
|
||||
<div class="flex flex-row items-center justify-center gap-1">
|
||||
<button
|
||||
type="submit"
|
||||
class:opacity-50={($events_sess.badges.search_status === 'loading') || (badges_loc.current.fulltext_search_qry_str ?? '').trim().length < effective_min_chars}
|
||||
class:pointer-events-none={($events_sess.badges.search_status === 'loading') || (badges_loc.current.fulltext_search_qry_str ?? '').trim().length < effective_min_chars}
|
||||
class="
|
||||
hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center justify-center gap-1 px-3 py-2 text-2xl font-bold transition-all duration-1000 hover:duration-300 min-w-0 preset-tonal-success rounded-lg border border-success-200-800
|
||||
hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center justify-center gap-1 px-3 py-2 text-2xl font-bold transition-all duration-1000 hover:duration-50 min-w-0 preset-tonal-success rounded-lg border border-success-200-800
|
||||
w-48
|
||||
">
|
||||
{#if $events_sess.badges.search_status === 'loading'}
|
||||
@@ -241,7 +250,10 @@ function handle_qr_scan_result(event: {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class:hidden={!badges_loc.current.fulltext_search_qry_str &&
|
||||
class:opacity-0={!badges_loc.current.fulltext_search_qry_str &&
|
||||
!badges_loc.current.search_badge_type_code &&
|
||||
badges_loc.current.qry_printed_status === 'all'}
|
||||
class:pointer-events-none={!badges_loc.current.fulltext_search_qry_str &&
|
||||
!badges_loc.current.search_badge_type_code &&
|
||||
badges_loc.current.qry_printed_status === 'all'}
|
||||
onclick={() => {
|
||||
@@ -254,7 +266,7 @@ function handle_qr_scan_result(event: {
|
||||
document.getElementById('badge_fulltext_search_qry_str')?.focus();
|
||||
}}
|
||||
class="
|
||||
hover:text-tertiary-800-200 hover:bg-tertiary-200-800 active:bg-surface-200-700 flex items-center justify-center gap-1 px-3 py-2 text-sm font-bold transition-all duration-1000 hover:duration-300 min-w-0 preset-outlined-tertiary rounded-lg border border-tertiary-200-800
|
||||
hover:text-tertiary-800-200 hover:bg-tertiary-200-800 active:bg-surface-200-700 flex items-center justify-center gap-1 px-3 py-2 text-sm font-bold transition-all duration-200 hover:duration-100 min-w-0 preset-outlined-tertiary rounded-lg border border-tertiary-200-800
|
||||
|
||||
"
|
||||
title="Clear search query">
|
||||
@@ -264,6 +276,7 @@ function handle_qr_scan_result(event: {
|
||||
|
||||
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}
|
||||
<label
|
||||
transition:fade={{ duration: 150 }}
|
||||
class="bg-surface-200-800 rounded-token flex cursor-pointer items-center gap-1 px-2 py-1 text-xs font-semibold">
|
||||
<span> Show Hidden </span>
|
||||
<input
|
||||
@@ -275,6 +288,7 @@ function handle_qr_scan_result(event: {
|
||||
{/if}
|
||||
{#if $ae_loc.edit_mode}
|
||||
<label
|
||||
transition:fade={{ duration: 150 }}
|
||||
class="bg-surface-200-800 rounded-token flex cursor-pointer items-center gap-1 px-2 py-1 text-xs font-semibold">
|
||||
<span> Remote First </span>
|
||||
<input
|
||||
@@ -288,6 +302,7 @@ function handle_qr_scan_result(event: {
|
||||
</form>
|
||||
{:else if $events_sess.badges.show_form__scan}
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
class="bg-surface-100-900 mx-auto w-full max-w-2xl rounded-lg p-4 shadow-lg">
|
||||
<Element_qr_scanner
|
||||
bind:start_qr_scanner={$events_sess.badges.qr_scan_start}
|
||||
|
||||
@@ -23,10 +23,11 @@ let upload_status: string = $state('idle'); // idle, loading, processing, succes
|
||||
let upload_message: string = $state('');
|
||||
let processed_badges_count: number = $state(0);
|
||||
let total_badges_in_file: number = $state(0);
|
||||
let upload_mode: 'client_csv' | 'axonius_zoom' = $state('axonius_zoom');
|
||||
let upload_mode: 'client_csv' | 'axonius_zoom' | 'axonius_splash_xlsx' = $state('axonius_zoom');
|
||||
let begin_at: number | null = $state(null);
|
||||
let end_at: number | null = $state(null);
|
||||
let return_detail: boolean = $state(false);
|
||||
let import_status_filter: string = $state('Attending'); // splash only; set to '' to import all statuses
|
||||
|
||||
// A very basic CSV parser that assumes the first row is headers
|
||||
function parse_csv(text: string): key_val[] {
|
||||
@@ -64,6 +65,72 @@ async function handle_upload(event: Event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Server-side import for Cvent Splash XLSX
|
||||
if (upload_mode === 'axonius_splash_xlsx') {
|
||||
upload_status = 'loading';
|
||||
upload_message = 'Uploading to server...';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', selected_file, selected_file.name);
|
||||
|
||||
const params: any = {};
|
||||
if (begin_at !== null && begin_at !== undefined) params.begin_at = String(begin_at);
|
||||
if (end_at !== null && end_at !== undefined) params.end_at = String(end_at);
|
||||
if (import_status_filter !== 'Attending') params.import_status_filter = import_status_filter;
|
||||
if (return_detail) params.return_detail = String(true);
|
||||
|
||||
const task_id = (crypto && (crypto as any).randomUUID)
|
||||
? (crypto as any).randomUUID()
|
||||
: String(Date.now());
|
||||
|
||||
try {
|
||||
upload_status = 'processing';
|
||||
upload_message = 'Processing on server… this may take a minute or two.';
|
||||
const endpoint = `/event/${event_id}/badge/import/splash_xlsx`;
|
||||
const result = await api.post_object({
|
||||
api_cfg: $ae_api,
|
||||
endpoint,
|
||||
form_data: formData,
|
||||
params,
|
||||
return_meta: true,
|
||||
task_id,
|
||||
timeout: 300000, // 5 min — server-side import can be slow; no retry to avoid duplicate imports
|
||||
retry_count: 1,
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
upload_status = 'error';
|
||||
upload_message = 'Server import failed (no response).';
|
||||
if (onerror) onerror(result);
|
||||
return;
|
||||
}
|
||||
|
||||
const meta = result.meta ?? result;
|
||||
if (meta && meta.success === false) {
|
||||
upload_status = 'error';
|
||||
upload_message = meta?.details?.message || 'Server import failed';
|
||||
if (onerror) onerror(result);
|
||||
return;
|
||||
}
|
||||
|
||||
const processed = meta?.processed ?? meta?.data_list_count ?? (Array.isArray(result?.data) ? result.data.length : 0);
|
||||
processed_badges_count = processed || 0;
|
||||
total_badges_in_file = meta?.total ?? processed_badges_count;
|
||||
|
||||
upload_status = 'success';
|
||||
upload_message = `Server processed ${processed_badges_count} badges.`;
|
||||
if (onsuccess) onsuccess();
|
||||
} catch (err: any) {
|
||||
upload_status = 'error';
|
||||
upload_message = `Upload failed: ${err?.message || 'Unknown error'}`;
|
||||
console.error('Upload error:', err);
|
||||
if (onerror) onerror(err);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Server-side import for Axonius Zoom CSV
|
||||
if (upload_mode === 'axonius_zoom') {
|
||||
upload_status = 'loading';
|
||||
@@ -83,6 +150,7 @@ async function handle_upload(event: Event) {
|
||||
|
||||
try {
|
||||
upload_status = 'processing';
|
||||
upload_message = 'Processing on server… this may take a minute or two.';
|
||||
const endpoint = `/event/${event_id}/badge/import/zoom_csv`;
|
||||
const result = await api.post_object({
|
||||
api_cfg: $ae_api,
|
||||
@@ -91,6 +159,8 @@ async function handle_upload(event: Event) {
|
||||
params,
|
||||
return_meta: true,
|
||||
task_id,
|
||||
timeout: 300000, // 5 min — server-side import can be slow; no retry to avoid duplicate imports
|
||||
retry_count: 1,
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
@@ -208,23 +278,27 @@ function handle_cancel() {
|
||||
<code>badge_type_code</code>.
|
||||
</p> -->
|
||||
<p>
|
||||
Upload the standard Axonius Zoom event tickets CSV export file here. This will trigger server-side processing to import the new and changed badges. This can take a few seconds to a few minutes.
|
||||
{#if upload_mode === 'axonius_splash_xlsx'}
|
||||
Upload the Cvent Splash XLSX registrant export file here. Only registrants with status "Attending" are imported by default. Server-side processing can take a few seconds to a few minutes.
|
||||
{:else}
|
||||
Upload the standard Axonius Zoom event tickets CSV export file here. This will trigger server-side processing to import the new and changed badges. This can take a few seconds to a few minutes.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<fieldset class="space-y-2">
|
||||
<legend class="label"><span>Upload format</span></legend>
|
||||
<div class="flex gap-4 items-center">
|
||||
<!-- <label class="label flex items-center gap-2">
|
||||
<input type="radio" name="upload_mode" value="client_csv" bind:group={upload_mode} />
|
||||
<span>Standard CSV (client-side parse)</span>
|
||||
</label> -->
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="radio" name="upload_mode" value="axonius_zoom" bind:group={upload_mode} />
|
||||
<span>Axonius Zoom CSV (event tickets)</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="radio" name="upload_mode" value="axonius_splash_xlsx" bind:group={upload_mode} />
|
||||
<span>Cvent Splash XLSX (registrant export)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if $ae_loc.administrator_access && upload_mode === 'axonius_zoom'}
|
||||
{#if $ae_loc.administrator_access && (upload_mode === 'axonius_zoom' || upload_mode === 'axonius_splash_xlsx')}
|
||||
<div class="grid grid-cols-3 gap-2 items-center">
|
||||
<input type="number" min="0" placeholder="begin_at (0)" bind:value={begin_at} class="input" />
|
||||
<input type="number" min="0" placeholder="end_at (20000)" bind:value={end_at} class="input" />
|
||||
@@ -233,14 +307,25 @@ function handle_cancel() {
|
||||
<span>Return detail</span>
|
||||
</label>
|
||||
</div>
|
||||
{#if upload_mode === 'axonius_splash_xlsx'}
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="label" for="import_status_filter">Status filter</label>
|
||||
<input
|
||||
id="import_status_filter"
|
||||
type="text"
|
||||
placeholder="Attending (leave blank for all)"
|
||||
bind:value={import_status_filter}
|
||||
class="input flex-1" />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</fieldset>
|
||||
|
||||
<label class="label">
|
||||
<span>Select CSV File</span>
|
||||
<span>Select {upload_mode === 'axonius_splash_xlsx' ? 'XLSX' : 'CSV'} File</span>
|
||||
<input
|
||||
type="file"
|
||||
accept=".csv"
|
||||
accept={upload_mode === 'axonius_splash_xlsx' ? '.xlsx' : '.csv'}
|
||||
bind:this={file_input}
|
||||
onchange={handle_file_change}
|
||||
class="input" />
|
||||
@@ -257,13 +342,15 @@ function handle_cancel() {
|
||||
class:preset-tonal-surface={upload_status !== 'error'}>
|
||||
<p>{upload_message}</p>
|
||||
{#if upload_status === 'processing' || upload_status === 'loading'}
|
||||
<progress
|
||||
class="progress"
|
||||
value={processed_badges_count}
|
||||
max={total_badges_in_file}></progress>
|
||||
<p>
|
||||
Processed: {processed_badges_count} / {total_badges_in_file}
|
||||
</p>
|
||||
{#if upload_mode === 'client_csv'}
|
||||
<progress
|
||||
class="progress"
|
||||
value={processed_badges_count}
|
||||
max={total_badges_in_file}></progress>
|
||||
<p>Processed: {processed_badges_count} / {total_badges_in_file}</p>
|
||||
{:else}
|
||||
<progress class="progress"></progress>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte core
|
||||
import { onMount } from 'svelte';
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
import * as Lucide from '@lucide/svelte';
|
||||
|
||||
// *** Import Aether core variables and functions
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
|
||||
@@ -73,12 +73,14 @@ let lq__post_comment_obj_li = $derived.by(() => {
|
||||
return liveQuery(async () => {
|
||||
if (!post_id) return [];
|
||||
|
||||
return await db_posts.comment
|
||||
// tmp_sort_1 is built with build_tmp_sort() and is designed for ASC sort:
|
||||
// priority=true -> '0', so priority records naturally sort first.
|
||||
const comments = await db_posts.comment
|
||||
.where('post_id')
|
||||
.equals(post_id)
|
||||
.reverse()
|
||||
.limit(limit)
|
||||
.sortBy('tmp_sort_1');
|
||||
return comments;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -255,8 +255,9 @@ async function handle_search_refresh(qry_key: string) {
|
||||
} else {
|
||||
// Robust Chronological Sort using pre-computed tmp_sort_1
|
||||
// Handles Priority, Manual Sort, and the updated_on/created_on fallback
|
||||
// tmp_sort_1 built by build_tmp_sort(): priority=true→'0', so ASC puts priority first.
|
||||
local_results.sort((a, b) =>
|
||||
(b.tmp_sort_1 ?? '').localeCompare(a.tmp_sort_1 ?? '')
|
||||
(a.tmp_sort_1 ?? '').localeCompare(b.tmp_sort_1 ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -335,8 +336,9 @@ async function handle_search_refresh(qry_key: string) {
|
||||
(b.name ?? '').localeCompare(a.name ?? '')
|
||||
);
|
||||
} else {
|
||||
// tmp_sort_1 built by build_tmp_sort(): priority=true→'0', so ASC puts priority first.
|
||||
api_results.sort((a, b) =>
|
||||
(b.tmp_sort_1 ?? '').localeCompare(a.tmp_sort_1 ?? '')
|
||||
(a.tmp_sort_1 ?? '').localeCompare(b.tmp_sort_1 ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,8 +95,10 @@ let lq__event_obj_li = $derived.by(() => {
|
||||
} else {
|
||||
// Robust Chronological Sort using pre-computed tmp_sort_1 (Refactored 2026-02-16)
|
||||
// This handles Group > Priority > Manual Sort > Date (with updated_on fallback)
|
||||
// tmp_sort_1 is built by build_tmp_sort() for ascending comparison:
|
||||
// priority=true encodes as '0', priority=false as '1', so ASC puts priority first.
|
||||
results.sort((a, b) =>
|
||||
(b.tmp_sort_1 ?? '').localeCompare(a.tmp_sort_1 ?? '')
|
||||
(a.tmp_sort_1 ?? '').localeCompare(b.tmp_sort_1 ?? '')
|
||||
);
|
||||
}
|
||||
return results;
|
||||
|
||||
@@ -432,6 +432,20 @@ async function handle_force_reset() {
|
||||
|
||||
let show_append_modal = $state(false);
|
||||
let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
|
||||
function handle_show_ai_tools() {
|
||||
show_config_modal = false;
|
||||
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const entry_id = $lq__journal_entry_obj?.journal_entry_id ?? 'default';
|
||||
const ai_tools_anchor_id = `journal_entry_ai_tools_${entry_id}`;
|
||||
|
||||
queueMicrotask(() => {
|
||||
const ai_tools_el = document.getElementById(ai_tools_anchor_id);
|
||||
ai_tools_el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="group/entry-view relative w-full min-w-0">
|
||||
@@ -488,13 +502,16 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
? 'ring-primary-500/40 ring-2 ring-inset'
|
||||
: ''}">
|
||||
{#if $ae_loc.edit_mode && $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'}
|
||||
<AE_Comp_Journal_Entry_AiTools
|
||||
content={typeof tmp_entry_obj.content === 'string'
|
||||
? tmp_entry_obj.content
|
||||
: ''}
|
||||
bind:summary={tmp_entry_obj.summary}
|
||||
on_save={() => update_journal_entry()}
|
||||
{log_lvl} />
|
||||
<div
|
||||
id={`journal_entry_ai_tools_${$lq__journal_entry_obj?.journal_entry_id ?? 'default'}`}>
|
||||
<AE_Comp_Journal_Entry_AiTools
|
||||
content={typeof tmp_entry_obj.content === 'string'
|
||||
? tmp_entry_obj.content
|
||||
: ''}
|
||||
bind:summary={tmp_entry_obj.summary}
|
||||
on_save={() => update_journal_entry()}
|
||||
{log_lvl} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<AE_Comp_Journal_Entry_Editor
|
||||
@@ -539,6 +556,7 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto');
|
||||
on_save={() => update_journal_entry()}
|
||||
on_force_reset={handle_force_reset}
|
||||
{on_show_export}
|
||||
on_show_ai={handle_show_ai_tools}
|
||||
on_append={() => {
|
||||
modal_mode = 'append';
|
||||
show_append_modal = true;
|
||||
|
||||
@@ -37,6 +37,7 @@ interface Props {
|
||||
on_save: () => void;
|
||||
on_force_reset?: () => void;
|
||||
on_show_export?: () => void;
|
||||
on_show_ai?: () => void;
|
||||
on_append?: () => void;
|
||||
on_prepend?: () => void;
|
||||
}
|
||||
@@ -49,6 +50,7 @@ let {
|
||||
on_save,
|
||||
on_force_reset,
|
||||
on_show_export,
|
||||
on_show_ai,
|
||||
on_append,
|
||||
on_prepend
|
||||
}: Props = $props();
|
||||
@@ -254,6 +256,16 @@ async function handle_admin_delete_action() {
|
||||
<FileDown size="1.2em" />
|
||||
Export Entry
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-primary w-full justify-start gap-2"
|
||||
onclick={() => {
|
||||
show = false;
|
||||
on_show_ai?.();
|
||||
}}>
|
||||
<Zap size="1.2em" />
|
||||
Summary AI
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-surface w-full justify-start gap-2"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { onMount } from 'svelte';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { load_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
|
||||
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
import * as Lucide from '@lucide/svelte';
|
||||
|
||||
let hosted_files: any[] = $state([]);
|
||||
let large_file_obj: any = $state(null);
|
||||
|
||||
9
src/types/temporary-svelte-augments.d.ts
vendored
9
src/types/temporary-svelte-augments.d.ts
vendored
@@ -45,13 +45,4 @@ declare module '@lucide/svelte' {
|
||||
export default _default;
|
||||
}
|
||||
|
||||
declare module 'lucide-svelte' {
|
||||
import type { SvelteComponentTyped } from 'svelte';
|
||||
export class IconBase extends SvelteComponentTyped<Record<string, any>, Record<string, any>, Record<string, any>> {}
|
||||
export class Star extends IconBase {}
|
||||
export class User extends IconBase {}
|
||||
const _default: { [key: string]: typeof IconBase | any };
|
||||
export default _default;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
Reference in New Issue
Block a user