docs: audit and archive completed Journals and Badges projects
This commit is contained in:
624
documentation/archive/PROJECT__AE_Events_Badges_Review_Print.md
Normal file
624
documentation/archive/PROJECT__AE_Events_Badges_Review_Print.md
Normal file
@@ -0,0 +1,624 @@
|
||||
# Archived Project: AE Events Badges — Review Form & Print Font Controls
|
||||
|
||||
**Created:** 2026-02-27
|
||||
**Completed and Archived:** 2026-06-12
|
||||
**Last Verified Against Source:** 2026-06-12
|
||||
**Branch:** `ae_app_3x_llm`
|
||||
**Owner:** Scott Idem / One Sky IT
|
||||
**Status:** Complete — review form, kiosk controls, auto-scaling, QR rendering, layouts, and print tracking are implemented.
|
||||
|
||||
The original project scope is complete and this document is retained as implementation history.
|
||||
Current behavior is documented in `documentation/MODULE__AE_Events_Badges.md` and
|
||||
`documentation/MODULE__AE_Events_Badge_Templates.md`. Remaining email-delivery and permission-config
|
||||
unification work is tracked in `documentation/TODO__Agents.md`. Planning statements later in this
|
||||
archived document describe the state at the time they were written and are not current instructions.
|
||||
|
||||
---
|
||||
|
||||
## Design Intent — Two Complementary Flows
|
||||
|
||||
### Flow 1: Remote Badge Review (email link)
|
||||
- Staff emails a review link to the attendee before the event.
|
||||
- Attendee opens the link on their own device, reviews their badge info, and edits permitted fields.
|
||||
- **Email address rule:** Always send to `event_badge.email` — never `email_override`.
|
||||
`email_override` is a display/badge field only. It cannot be trusted as a delivery address
|
||||
(attendee may have changed it to something different for badge display purposes).
|
||||
- Component: `ae_comp__badge_review_form.svelte` — plain form, no badge render.
|
||||
- Route: `/events/[event_id]/badges/[badge_id]/review/`
|
||||
|
||||
### Flow 2: Kiosk / Onsite Badge Station (print page)
|
||||
- Hardware: a laptop + badge printer (Epson fanfold or Zebra PVC card) at the check-in table.
|
||||
- At the event, an attendee walks up to a badge station (check-in kiosk).
|
||||
- A staff member or volunteer pulls up the attendee's badge on the print page.
|
||||
- The **print page is a kiosk tool**, not just a print queue:
|
||||
- Attendee reviews their badge info and can edit permitted fields **in real time**,
|
||||
with the live badge render updating as they make changes.
|
||||
- Staff/volunteers are present to assist with any questions.
|
||||
- Once satisfied, staff prints the badge.
|
||||
- The key differentiator vs the review form: **the live badge render** shows exactly how
|
||||
the badge will print. Attendees and staff can see changes immediately.
|
||||
- Component: `ae_comp__badge_obj_view.svelte`
|
||||
- Route: `/events/[event_id]/badges/[badge_id]/print/`
|
||||
|
||||
### Permission Model — Same Logic, Both Flows
|
||||
Both flows should respect the same permission model:
|
||||
- **Attendee-level** (basic Authenticated access): can edit `pronouns_override`,
|
||||
`full_name_override`, `professional_title_override`, `affiliations_override`,
|
||||
`location_override`, `phone_override`, `email_override`, `allow_tracking`, `agree_to_tc`.
|
||||
- **Staff-level** (trusted_access+): all attendee fields + `email`, `badge_type_code_override`,
|
||||
`badge_type_override`, `hide`, `priority`, `notes`, and font size controls.
|
||||
- Permissions are configured per-event in `event.mod_badges_json.edit_permissions`.
|
||||
Hardcoded defaults are used until that config is implemented.
|
||||
|
||||
**Task 4 outcome:** The print controls now implement field-level editing. Authenticated users
|
||||
can edit template-approved fields, trusted staff can correct names, and trusted staff in global
|
||||
Edit Mode can edit all fields. First printing is available at public kiosk access; reprinting
|
||||
requires trusted access plus Edit Mode. Remote review uses event-level `edit_permissions`, while
|
||||
the print controls currently use template-level `controls_cfg`; unification is tracked separately.
|
||||
|
||||
---
|
||||
|
||||
## Task 4 Outcomes
|
||||
|
||||
### 0. Kiosk Editing — Complete
|
||||
|
||||
`ae_comp__badge_print_controls.svelte` provides the inline controls and live preview. Its default
|
||||
authenticated fields are title, affiliations, location, lead tracking, and pronouns; template
|
||||
`controls_cfg` can narrow the fields shown and editable. Email delivery remains a placeholder;
|
||||
when implemented it must send to `event_badge.email`, never `email_override`.
|
||||
|
||||
### 1. Auto-Scaling Badge Text — Complete
|
||||
`ae_comp__badge_obj_view.svelte` using `element_fit_text.svelte` (binary search auto-scale).
|
||||
Toggle between v1 (heuristic) and v2 (auto-scale) on the print page via the `v1`/`v2` header button.
|
||||
Heights tuned per layout in `fit_heights` derived object. Still needs visual tuning with real badges.
|
||||
|
||||
### 2. QR Code on Badge Front — `ae_comp__badge_obj_view.svelte`
|
||||
The badge template has a `show_qr` flag (or similar). When toggled on, the QR code should
|
||||
appear on the front face of the printed badge. Currently QR is only shown on the review form.
|
||||
|
||||
- Check the badge template fields for the QR toggle field name (`show_qr`, `qr_enabled`, etc.)
|
||||
via `ae_describe event_badge_template` and inspect `ae_comp__badge_obj_view.svelte`.
|
||||
- The QR code data URL is generated with:
|
||||
```typescript
|
||||
qr_data_url = await core_func.js_generate_qr_code('obj', {
|
||||
obj_type: 'event_badge',
|
||||
obj_id: event_badge_id
|
||||
});
|
||||
```
|
||||
See `ae_comp__badge_review_form.svelte` for the working pattern.
|
||||
- Position on badge: typically bottom-right corner of the badge face, sized to fit within
|
||||
the template's layout constraints. Do NOT alter structural badge dimensions.
|
||||
- Must be hidden on `ae_comp__badge_obj_view.svelte` when `show_qr` is falsy.
|
||||
|
||||
### 2. Badge Print Controls — UX Improvements (ae_comp__badge_print_controls.svelte)
|
||||
- Consider: keyboard shortcuts (+ / -) for font sizing while a field is active
|
||||
- Consider: "Apply to all badges" workflow for font size presets
|
||||
|
||||
### 4. Leads Module
|
||||
Next major work after badge polish. See `documentation/MODULE__AE_Events_Leads.md` (if it
|
||||
exists) for context. Exhibitor lead scanning via QR code at exhibitor booth → capture attendee
|
||||
badge data, gated by `allow_tracking` on the badge.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ TASK 4.0: Kiosk Editing — COMPLETE (verified 2026-06-12)
|
||||
The print controls implement authenticated field editing, trusted name correction, trusted + Edit
|
||||
Mode full editing, and live preview. The print path uses template `controls_cfg`; the review path
|
||||
uses event `mod_badges_json.edit_permissions`. Aligning those configuration sources is a follow-up,
|
||||
not a blocker to the completed kiosk controls.
|
||||
|
||||
**Note (2026-03-18):** `style_href` and `duplex` are both fully implemented and verified in code —
|
||||
the MODULE doc TODO list was stale. `duplex` is in `properties_to_save`; v2 badge render gates
|
||||
`show_badge_back` on it. `style_href` loads via `<svelte:head>` in `print/+page.svelte`.
|
||||
|
||||
### ✅ TASK 4.1: Auto-Scaling Badge Text v2 — COMPLETE (2026-03-12)
|
||||
**Files created/updated:**
|
||||
- `src/lib/elements/action_fit_text.ts` — Svelte action
|
||||
- `src/lib/elements/element_fit_text.svelte` — Component wrapper
|
||||
- `src/routes/events/.../ae_comp__badge_obj_view.svelte` — V2 badge render (canonical)
|
||||
Debug blocks gated behind `$ae_loc.edit_mode` (hidden in production).
|
||||
- `print/+page.svelte` — Always uses v2 now. v1/v2 toggle removed. Header redesigned for kiosk UX.
|
||||
- `ae_comp__badge_print_controls.svelte` — Identity card at top, pronouns moved to attendee section,
|
||||
"Staff adjustments" divider before badge_type field.
|
||||
- `print_list/+page.svelte` — Updated to import v2.
|
||||
- `ae_comp__badge_obj_view.svelte` (v1) — **Moved to ~/tmp/agents_trash/**
|
||||
|
||||
**Kiosk UX improvements (2026-03-12):**
|
||||
- Print page header: cleaner, shows name + "Ready"/"Printed N×" status chip, event name.
|
||||
Header Print Now button removed (duplicate); only Re-print shortcut visible in trusted+edit mode.
|
||||
- Controls right panel: identity card at top confirms who the badge belongs to before printing.
|
||||
Pronouns field is now an attendee-level field (was trusted-only). Staff section labelled.
|
||||
- Debug JSON blocks in v2 badge render hidden behind global edit_mode flag.
|
||||
|
||||
**Print page CSS centering work (also 2026-03-12) — ⚠️ Chromium PDF issue pending:**
|
||||
Multiple iterations to center the badge on the printed page, working around SvelteKit layout
|
||||
hierarchy issues. Current approach: `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%)`
|
||||
on `.event_badge_wrapper`. Key findings:
|
||||
|
||||
- `#ae_main_content` has `overflow: auto` — Firefox (spec-compliant) won't let `display: contents`
|
||||
dissolve it. Workaround: explicit passthrough block (`display: block; overflow: visible; width: 100%`).
|
||||
- `app.css` global `overflow: hidden` on `html, body` creates a BFC that collapses body to badge-width.
|
||||
Override with `overflow: visible !important` in print CSS.
|
||||
- **Chrome ignores `@page { size }` for Save as PDF** — verified 2026-03-12. Firefox honors it.
|
||||
Chrome uses the system default paper size; Firefox locks to the CSS `@page { size }` value.
|
||||
Neither browser lets you change paper size in "Save to PDF" mode — only when printing to a
|
||||
physical printer. For actual Epson/Zebra printing, the driver controls paper size; unaffected.
|
||||
- **Chrome "Default" margins cause the "squish"** — Chrome's Default margin setting inserts
|
||||
URL/date/page-number headers and footers into the printable area. These eat into the space
|
||||
that `position: fixed; top: 50%` references, making the badge off-center or clipped. Fix:
|
||||
set **Margins → None** (or Minimum) in Chrome's print dialog. Content centering verified
|
||||
correct: Chrome A4 + None margins produces badge center = 297.5 pts on a 297.5 pt wide page.
|
||||
Use Firefox for accurate PDF proofing.
|
||||
- See `documentation/MODULE__AE_Events_Badge_Templates.md` → "Print Layout Architecture" for full
|
||||
technical details.
|
||||
|
||||
### ✅ TASK 3: Badge Print Controls Panel — COMPLETE (2026-03-02)
|
||||
|
||||
**Files created/modified:**
|
||||
- `ae_comp__badge_print_controls.svelte` — NEW. Right-edge control panel with per-field
|
||||
accordion sections. Font size controls + inline edit forms gated by access level.
|
||||
- `print/+page.svelte` — layout changed from `flex-row` to fixed right panel.
|
||||
|
||||
**Design decisions:**
|
||||
- Controls panel is `position: fixed right-0 top-20 bottom-0 w-64` — out of normal flow,
|
||||
always visible regardless of viewport width. `top-20` (80px) clears the page header.
|
||||
- Badge area gets `pr-64` to prevent content from hiding under the fixed panel.
|
||||
`print:pr-0` and `print:hidden` on the panel restore a clean print layout.
|
||||
- `bg-white dark:bg-zinc-900` gives the panel a solid background to prevent bleed-through.
|
||||
|
||||
**Per-field accordion structure (one open at a time):**
|
||||
|
||||
| Field | Access | Font Controls |
|
||||
| --- | --- | --- |
|
||||
| Name | Trusted+ edit | ✅ |
|
||||
| Professional Title | All auth edit | ✅ |
|
||||
| Affiliations | All auth edit (textarea) | ✅ |
|
||||
| Location | All auth edit | ✅ |
|
||||
| Lead Scanning (allow_tracking) | All auth edit | — |
|
||||
| Pronouns | Trusted+ edit | — |
|
||||
| Badge Type | Trusted+, only when template has badge_type_list | — |
|
||||
|
||||
**Access level note:**
|
||||
`is_trusted = $derived($ae_loc.trusted_access === true)` — covers Trusted, Administrator,
|
||||
Manager, Super (cascade). No need to OR in `administrator_access`.
|
||||
|
||||
**badge_type_override coupling:**
|
||||
When badge type is changed via dropdown, both `badge_type_code_override` AND
|
||||
`badge_type_override` are saved together (name comes from template list). Same behavior in
|
||||
`ae_comp__badge_obj_view.svelte` and `ae_comp__badge_review_form.svelte`.
|
||||
Edge case: custom names (e.g. code=`member`, name=`"Life Member"`) must be set manually in DB.
|
||||
|
||||
**Font size config (moved from print page to controls component):**
|
||||
|
||||
| Field | Default px | Range | Step |
|
||||
|--------------|------------|----------|------|
|
||||
| Name | 58px | 20–80px | 2px |
|
||||
| Title | 34px | 14–56px | 2px |
|
||||
| Affiliations | 38px | 14–60px | 2px |
|
||||
| Location | 34px | 14–56px | 2px |
|
||||
|
||||
Font sizes flow back to the parent via `$bindable()` props so `ae_comp__badge_obj_view`
|
||||
stays in sync without prop-drilling through a third component.
|
||||
|
||||
---
|
||||
|
||||
### ✅ TASK 1: Badge Review Form — COMPLETE
|
||||
|
||||
The badge review form (`ae_comp__badge_review_form.svelte`) is now fully functional with:
|
||||
- ✅ All editable fields with access-level gating
|
||||
- ✅ Print status display section
|
||||
- ✅ QR code generation and display (hover zoom + click expand)
|
||||
- ✅ Options and Tickets fields (staff edit / attendee view)
|
||||
- ✅ Save/Cancel with change detection
|
||||
- ✅ Override field revert buttons
|
||||
- ✅ **HTML rendering** for full_name, professional_title, affiliations, location
|
||||
- ✅ **Accessibility toggle** for text enlargement (text-2xl ↔ text-4xl)
|
||||
- ✅ **Help modal** with 6 sections of attendee guidance (Flowbite Modal component)
|
||||
- ✅ Local edit mode (never writes to `$ae_loc.edit_mode`)
|
||||
|
||||
**Bug fixed (2026-02-27):** `default_authenticated_fields` and `default_trusted_fields` in
|
||||
`review/+page.svelte` had incorrect field names causing `can_edit()` to silently drop saves.
|
||||
Fixed to use exact names matching the form's `can_edit()` checks.
|
||||
|
||||
### ✅ TASK 2: Badge Print Font Controls — COMPLETE (v1)
|
||||
|
||||
Implemented in commit `3d7279da`. This is a **first draft** — auto font scaling using
|
||||
mm/inch units is planned as a future iteration.
|
||||
|
||||
**What was built:**
|
||||
- `ae_comp__badge_obj_view.svelte`: 4 new optional props (`font_size_name`, `font_size_title`,
|
||||
`font_size_affiliations`, `font_size_location`, all `number` in px). When provided, replaces
|
||||
auto inch-based Tailwind class sizing with an inline `font-size: Npx` style. Existing
|
||||
auto-sizing behavior is completely unchanged when props are absent.
|
||||
- `print/+page.svelte`: Screen-only (`print:hidden`) control panel with 4 rows, one per field.
|
||||
Each row: label, `[−]` button, value display (`58px` or `Auto`), `[+]` button, `[↺]` reset.
|
||||
Step: 2px. `null` state = auto (uses existing inch-based auto-sizing). First `+` click
|
||||
activates at a sensible default that approximates the current auto inch values.
|
||||
|
||||
**Default px values when first activated (≈ inch equivalents at 96dpi):**
|
||||
|
||||
| Field | Default px | Approx. inch | Range |
|
||||
|--------------|------------|--------------|----------|
|
||||
| Name | 58px | ≈ .60in | 20–80px |
|
||||
| Title | 34px | ≈ .35in | 14–56px |
|
||||
| Affiliations | 38px | ≈ .40in | 14–60px |
|
||||
| Location | 34px | ≈ .35in | 14–56px |
|
||||
|
||||
**Future:** Auto font scaling using mm/inch units (physical paper stock measurements).
|
||||
Will likely need to revisit the inch ↔ mm conversion and potentially expose the auto-sizing
|
||||
logic as adjustable rather than replacing it with px overrides.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The Events Badges module is mostly complete for navigation and search. Two key pieces of
|
||||
functional UI were needed before the first show:
|
||||
|
||||
1. **Badge Review Form** ✅ — `ae_comp__badge_review_form.svelte` now has complete field
|
||||
rendering, edit inputs gated by access level, save/cancel API calls, and display-only
|
||||
sections (QR code, print status, option/ticket checkmarks). Also includes accessibility
|
||||
features (text enlargement) and help modal for attendee guidance.
|
||||
|
||||
2. **Badge Print Font Controls** ⏳ — The print page header needs screen-only controls
|
||||
(hidden during `window.print()`) to bump font sizes for the name, professional title,
|
||||
affiliations, and location sections before printing. These only affect the `ae_comp__badge_obj_view.svelte` render — not the page layout/template structural dimensions.
|
||||
|
||||
Read `documentation/MODULE__AE_Events_Badges.md` for full module context before starting.
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY: Before You Start
|
||||
|
||||
1. Run `ae_describe event_badge` (MCP tool) to confirm which fields actually exist in the
|
||||
DB. Several fields in the spec below may need to be added to `properties_to_save` in
|
||||
`src/lib/ae_events/ae_events__event_badge.ts` if they are not already saved to IDB.
|
||||
|
||||
2. Fields to specifically confirm exist in `event_badge` schema:
|
||||
- `pronouns`, `pronouns_override`
|
||||
- `phone`, `phone_override`
|
||||
- `allow_tracking`
|
||||
- `agree_to_tc`
|
||||
- `other_1_code` through `other_8_code` (the "option" fields)
|
||||
- `ticket_1_code` through `ticket_8_code`
|
||||
- `registration_type`, `registration_type_code`
|
||||
- `registration_type_override`, `registration_type_code_override`
|
||||
|
||||
3. Run `npx svelte-check` before committing. Baseline is **77 errors** (all pre-existing,
|
||||
none in the badge module files). Do not introduce new errors.
|
||||
|
||||
4. Do NOT write to `$ae_loc.edit_mode` from any badge component. This was a critical
|
||||
bug (fixed 2026-02-27). See `documentation/AE__Permissions_and_Security.md`.
|
||||
|
||||
---
|
||||
|
||||
## TASK 1: Badge Review Form (HIGH PRIORITY)
|
||||
|
||||
### File to build
|
||||
`src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_review_form.svelte`
|
||||
|
||||
This component is already imported and used by `review/+page.svelte`. Props it receives:
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
event_id: string;
|
||||
event_badge_id: string;
|
||||
lq__event_badge_obj: any; // Svelte 5 store from liveQuery
|
||||
can_edit_fields: string[]; // Which fields this user can edit
|
||||
is_staff: boolean; // True if trusted_access or higher
|
||||
log_lvl?: number;
|
||||
}
|
||||
```
|
||||
|
||||
`can_edit_fields` values:
|
||||
- `['*']` — administrator (all fields)
|
||||
- Array of field names — specific editable fields
|
||||
- `[]` — read-only (shouldn't normally reach this component, but handle it)
|
||||
|
||||
### Helper
|
||||
|
||||
Use a helper derived inside the component:
|
||||
```typescript
|
||||
function can_edit(field: string): boolean {
|
||||
return can_edit_fields.includes('*') || can_edit_fields.includes(field);
|
||||
}
|
||||
```
|
||||
|
||||
### Save / Cancel Pattern
|
||||
|
||||
Follow the Journals module pattern (`src/lib/ae_journals/`). Key points:
|
||||
- Use `import { events_func } from '$lib/ae_events_functions'`
|
||||
- Call `events_func.update_ae_obj__event_badge({ event_badge_id, event_id, data_kv })`
|
||||
- Only send changed fields in `data_kv` (compare against `$lq__event_badge_obj` values)
|
||||
- Show save/cancel buttons only when something has changed (`has_changes` derived)
|
||||
- Show a success/error state briefly after save (1-2 seconds, then reset)
|
||||
- Cancel resets local state back to `$lq__event_badge_obj` values
|
||||
- Use `data-testid="badge-review-save-btn"` and `data-testid="badge-review-cancel-btn"`
|
||||
|
||||
### Save API Call
|
||||
|
||||
```typescript
|
||||
await events_func.update_ae_obj__event_badge({
|
||||
api_cfg: $ae_api, // from ae_loc store or passed as prop — check how ae_comp__badge_obj_view.svelte does it
|
||||
event_badge_id: event_badge_id,
|
||||
event_id: event_id,
|
||||
data_kv: { /* only changed fields */ }
|
||||
});
|
||||
```
|
||||
|
||||
Check `ae_comp__badge_obj_view.svelte` for the existing save pattern — it already works
|
||||
and can be used as reference.
|
||||
|
||||
---
|
||||
|
||||
### Section 1: Display-Only Status Bar (all access levels) ✅ IMPLEMENTED
|
||||
|
||||
Always show at top of form. Read-only. No edit controls.
|
||||
|
||||
```
|
||||
Print Status: [Not yet printed] OR [Printed 3× — first: Jan 5 2026, last: Jan 5 2026]
|
||||
```
|
||||
|
||||
**Implemented:** Shows print count with first/last print datetimes. Hidden if `print_count < 1`.
|
||||
Uses `$lq__event_badge_obj.print_count`, `print_first_datetime`, `print_last_datetime`.
|
||||
Format datetimes with `ae_util.iso_datetime_formatter(dt, 'datetime_iso_12_no_seconds')`.
|
||||
Import `ae_util` from `$lib/ae_utils/ae_utils`.
|
||||
|
||||
---
|
||||
|
||||
### Section 2: QR Code (all access levels) ✅ IMPLEMENTED
|
||||
|
||||
Display the attendee's badge QR code. This is the same QR code shown on the printed badge
|
||||
itself — scanning it at the badge station triggers automatic badge search and print.
|
||||
|
||||
**Implemented using `core_func.js_generate_qr_code()`:**
|
||||
```typescript
|
||||
qr_data_url = await core_func.js_generate_qr_code('obj', {
|
||||
obj_type: 'event_badge',
|
||||
obj_id: event_badge_id
|
||||
});
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Hover: Zoom overlay effect (`qr_hovered` state)
|
||||
- Click: Expand/collapse that pushes content down (`qr_expanded` state)
|
||||
- Displays as data URL image from QR code generation
|
||||
- Reactive: automatically regenerates when `event_badge_id` changes
|
||||
|
||||
---
|
||||
|
||||
### Section 3: Editable Fields ✅ IMPLEMENTED
|
||||
|
||||
Render each field as: read-only display when `!can_edit(field)`, or an `<input>` /
|
||||
`<select>` / `<textarea>` when `can_edit(field)`.
|
||||
|
||||
Show `(overridden)` label next to override fields when the override value differs from
|
||||
the base field value.
|
||||
|
||||
**HTML Rendering (implemented 2026-02-27):**
|
||||
The following fields render HTML markup using `{@html}` when viewing (not when editing):
|
||||
- `full_name_override` / `full_name`
|
||||
- `professional_title_override` / `professional_title`
|
||||
- `affiliations_override` / `affiliations`
|
||||
- `location_override` / `location`
|
||||
|
||||
This allows for rich text formatting (bold, italic, line breaks, etc.) in badge displays.
|
||||
|
||||
**Accessibility Features (implemented 2026-02-27):**
|
||||
- Text enlargement toggle button in sticky header
|
||||
- Normal size: `text-2xl` on field values
|
||||
- Enlarged size: `text-4xl` on field values
|
||||
- Button shows visual feedback (gray → blue, "Larger" → "Normal" label)
|
||||
- Applied consistently across all text fields
|
||||
|
||||
**Help Modal (implemented 2026-02-27):**
|
||||
- Flowbite Modal component with 6 sections
|
||||
- Sections: Reviewing Badge, Editing Info, Accessibility, QR Code, Lead Scanning, Assistance
|
||||
- Triggered by Help button (BadgeQuestionMark icon) in sticky header
|
||||
- Currently has placeholder text — can be customized per event/client
|
||||
|
||||
#### Attendee-Editable Fields (shown to all access levels with link)
|
||||
|
||||
| Field | Input Type | Notes |
|
||||
|---|---|---|
|
||||
| `pronouns_override` | text input | Fallback display: `pronouns` |
|
||||
| `full_name_override` | text input | Fallback display: `full_name`; **renders HTML** when viewing |
|
||||
| `professional_title_override` | text input | Fallback display: `professional_title`; **renders HTML** when viewing |
|
||||
| `affiliations_override` | textarea | Fallback display: `affiliations`; **renders HTML** when viewing |
|
||||
| `phone_override` | text input (tel) | Fallback display: `phone` |
|
||||
| `location_override` | text input | Fallback display: `location`; **renders HTML** when viewing |
|
||||
| `allow_tracking` | checkbox | Label: "Allow exhibitor lead scanning" |
|
||||
| `agree_to_tc` | checkbox | Label: "I agree to the Terms and Conditions" + placeholder T&C text block |
|
||||
|
||||
#### Staff-Only Additional Fields (shown when `is_staff === true`) ✅ IMPLEMENTED
|
||||
|
||||
| Field | Input Type | Notes |
|
||||
|---|---|---|
|
||||
| `email_override` | email input | Fallback display: `email` |
|
||||
| `badge_type_code_override` | select | Options: member, non-member, guest, exhibitor, staff, test; also updates `badge_type_override` text |
|
||||
|
||||
> **Edge case — custom badge type name:** If an attendee needs a standard badge type code (for
|
||||
> CSS styling) but a slightly different displayed name (e.g. code=`member`, name=`"Life Member"`),
|
||||
> set `badge_type_override` directly in the DB. Do **not** use the dropdown — selecting from the
|
||||
> dropdown in the UI overwrites `badge_type_override` with the standard name from the template
|
||||
> list. This is an intentional trade-off: coded for the normal case (dropdown keeps both fields in
|
||||
> sync), special cases handled manually by Scott in the DB.
|
||||
| `registration_type_code_override` | select | Same options as badge_type for now; also updates `registration_type_override` |
|
||||
| `hide` | checkbox | Label: "Hidden from search results" |
|
||||
| `priority` | number input | |
|
||||
| `notes` | textarea | |
|
||||
|
||||
#### Staff-Only: Options & Tickets (read-edit, shown when `is_staff === true`) ✅ IMPLEMENTED
|
||||
|
||||
**Other/Options** (`other_1_code` through `other_8_code`):
|
||||
- If field has a value: show as editable text input with label "Option X"
|
||||
- If field is empty/null: show faintly as "Option X (empty)" — staff can still set it
|
||||
- These represent event-specific add-ons or membership indicators
|
||||
|
||||
**Tickets** (`ticket_1_code` through `ticket_8_code`):
|
||||
- Same pattern as options above, label "Ticket X"
|
||||
|
||||
#### Attendee-Only: Options & Tickets (display only) ✅ IMPLEMENTED
|
||||
|
||||
When `!is_staff` and the field has a value: show `[✓] Option X` or `[✓] Ticket X`.
|
||||
When the field is empty: hide entirely (attendees don't see empty slots).
|
||||
|
||||
---
|
||||
|
||||
### Section 4: Terms & Conditions Block (all, only when `agree_to_tc` in can_edit_fields) ✅ IMPLEMENTED
|
||||
|
||||
Placeholder text for now:
|
||||
```
|
||||
By checking this box, I confirm that the information on my badge is correct to the best
|
||||
of my knowledge. I agree that this badge may be used for identification purposes during
|
||||
the event and that my attendance may be recorded by exhibitors using the lead scanning
|
||||
feature if I permit it.
|
||||
```
|
||||
|
||||
Show this before the `agree_to_tc` checkbox. If `agree_to_tc` is not in `can_edit_fields`,
|
||||
hide the entire block.
|
||||
|
||||
---
|
||||
|
||||
### Field State Pattern (Svelte 5 runes)
|
||||
|
||||
```typescript
|
||||
// Initialize local editable state from badge object
|
||||
let local_full_name_override = $state($lq__event_badge_obj?.full_name_override ?? '');
|
||||
let local_pronouns_override = $state($lq__event_badge_obj?.pronouns_override ?? '');
|
||||
// ... etc for each editable field
|
||||
|
||||
// Detect changes
|
||||
let has_changes = $derived(
|
||||
local_full_name_override !== ($lq__event_badge_obj?.full_name_override ?? '')
|
||||
|| local_pronouns_override !== ($lq__event_badge_obj?.pronouns_override ?? '')
|
||||
// ... etc
|
||||
);
|
||||
|
||||
// Build changed-fields-only payload
|
||||
function build_save_payload(): Record<string, any> {
|
||||
const payload: Record<string, any> = {};
|
||||
if (local_full_name_override !== ($lq__event_badge_obj?.full_name_override ?? ''))
|
||||
payload.full_name_override = local_full_name_override || null; // empty string → null
|
||||
// ... etc
|
||||
return payload;
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Empty string inputs should save as `null` (clears the override, falls back
|
||||
to base field). Use `value || null` in the payload.
|
||||
|
||||
---
|
||||
|
||||
## TASK 2: Badge Print Font Size Controls (MEDIUM PRIORITY)
|
||||
|
||||
### Where to add
|
||||
|
||||
`src/routes/events/[event_id]/(badges)/badges/[badge_id]/print/+page.svelte`
|
||||
|
||||
Add a screen-only (`print:hidden`) control panel between the header and the badge render.
|
||||
This panel lets staff adjust font sizes for the four text-heavy sections before clicking Print.
|
||||
|
||||
### Controls needed
|
||||
|
||||
```
|
||||
Font Size Controls (screen only, hidden during print):
|
||||
[Name] [−] [14px] [+]
|
||||
[Title] [−] [12px] [+]
|
||||
[Affiliations] [−] [11px] [+]
|
||||
[Location] [−] [10px] [+]
|
||||
```
|
||||
|
||||
- Start with sensible defaults (match what `ae_comp__badge_obj_view.svelte` currently uses)
|
||||
- Min/max per field (e.g., 8px–24px for name, 7px–18px for others)
|
||||
- Pass the sizes as props into `ae_comp__badge_obj_view`
|
||||
|
||||
### Props to add to `ae_comp__badge_obj_view.svelte`
|
||||
|
||||
`ae_comp__badge_obj_view.svelte` currently has internal font size logic. It needs to
|
||||
accept optional override props:
|
||||
|
||||
```typescript
|
||||
// New optional props:
|
||||
font_size_name?: number; // px
|
||||
font_size_title?: number; // px
|
||||
font_size_affiliations?: number; // px
|
||||
font_size_location?: number; // px
|
||||
```
|
||||
|
||||
When these props are provided, use them instead of the internally computed sizes.
|
||||
When not provided, fall back to existing auto-sizing behavior.
|
||||
|
||||
**IMPORTANT:** Do NOT touch structural dimensions (overall badge width/height, header/footer
|
||||
sizes, template layout). Only the text content font sizes.
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `[badge_id]/ae_comp__badge_review_form.svelte` | **BUILD THIS** — review form stub |
|
||||
| `[badge_id]/ae_comp__badge_obj_view.svelte` | Badge render + print button; add font size props |
|
||||
| `[badge_id]/print/+page.svelte` | Print page; add font size control panel |
|
||||
| `[badge_id]/review/+page.svelte` | Review page; already wired, passes `can_edit_fields` |
|
||||
| `src/lib/ae_events/ae_events__event_badge.ts` | API functions: `update_ae_obj__event_badge` |
|
||||
| `src/lib/ae_events/db_events.ts` | Dexie schema — `properties_to_save` for badge |
|
||||
| `src/lib/ae_utils/ae_utils.ts` | `ae_util.iso_datetime_formatter()` |
|
||||
| `documentation/MODULE__AE_Events_Badges.md` | Full module reference |
|
||||
| `documentation/AE__Permissions_and_Security.md` | Permission flags, edit_mode rules |
|
||||
| `documentation/GUIDE__AE_API_V3_for_Frontend.md` | V3 API reference |
|
||||
|
||||
## Access Level Reference
|
||||
|
||||
```typescript
|
||||
// From $ae_loc store (persisted localStorage)
|
||||
$ae_loc.trusted_access // true = trusted and above (onsite staff)
|
||||
$ae_loc.administrator_access // true = administrator and above
|
||||
$ae_loc.edit_mode // boolean — user preference toggle (NEVER write to this from components)
|
||||
```
|
||||
|
||||
`is_staff` prop on the review form = `$ae_loc.trusted_access`.
|
||||
`trusted_access` is `true` for Trusted and every level above it (Administrator, Manager, Super)
|
||||
— no need to OR in `administrator_access` since it's already implied by the cascade.
|
||||
|
||||
---
|
||||
|
||||
## Patterns to Follow
|
||||
|
||||
- **Canonical module reference:** `src/lib/ae_journals/` — most complete, most advanced
|
||||
- **Svelte 5 runes:** `$state`, `$derived`, `$derived.by()`, `$effect` — no legacy `$:` syntax
|
||||
- **Icons:** Lucide Svelte only — `import { Save, X, Check, ... } from 'lucide-svelte'`
|
||||
- **No Font Awesome** (`fas fa-*`) anywhere in the badge module
|
||||
- **Styling:** Tailwind CSS v4 + Skeleton UI utility classes (`btn`, `preset-tonal-*`, `input`, `card`)
|
||||
- **Commits:** Atomic — one component per commit; run `npx svelte-check` before every commit
|
||||
|
||||
---
|
||||
|
||||
## What NOT to Do
|
||||
|
||||
- Do NOT touch `@page` CSS or badge template structural dimensions — print layout is out of scope
|
||||
- Do NOT write to `$ae_loc.edit_mode` from any component
|
||||
- Do NOT connect `mod_badges_json.edit_permissions` yet — hardcoded field lists are intentional for now
|
||||
- Do NOT implement the email API — `send_review_email()` placeholder stays as `alert()`
|
||||
- Do NOT add `person_passcode` DB field — out of scope for this sprint
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Run existing badge tests after any changes:
|
||||
```bash
|
||||
npm run test:unit
|
||||
npx playwright test tests/events/badges/
|
||||
```
|
||||
|
||||
Baseline: all badge tests passing as of 2026-02-26 (`f5e98b8c`).
|
||||
|
||||
Add `data-testid` attributes to key interactive elements:
|
||||
- `badge-review-save-btn`
|
||||
- `badge-review-cancel-btn`
|
||||
- `badge-review-full-name-input`
|
||||
- `badge-review-agree-to-tc-checkbox`
|
||||
@@ -0,0 +1,195 @@
|
||||
# Archived Project: Aether Journals UI Update (2026)
|
||||
|
||||
> **Status:** Completed and archived 2026-06-12
|
||||
> **Last Verified Against Source:** 2026-06-12
|
||||
> **Primary Agent:** Frontend SvelteKit Agent
|
||||
|
||||
The UI modernization scope is complete: V3 CRUD, Quick Add, Append/Prepend,
|
||||
import/export, auto-save, configuration modals, decryption isolation, and the
|
||||
Journals style pass are implemented. Unfinished security and product follow-ups
|
||||
were transferred to `documentation/TODO__Agents.md`; current operational behavior
|
||||
and limitations live in `documentation/MODULE__AE_Journals.md`.
|
||||
|
||||
## 1. Project Overview
|
||||
This document outlines the modernization of the Journals module UI in the SvelteKit frontend (`aether_app_sveltekit`). The primary goals are to fully leverage the generic V3 API architecture and introduce high-velocity productivity features for journal management.
|
||||
|
||||
**Context:** The backend transition to the generic `api_crud` router is complete. Custom legacy routers have been removed. The frontend must now fully align with this pattern and provide a frictionless user experience.
|
||||
|
||||
---
|
||||
|
||||
## 2. Core Objectives
|
||||
|
||||
### 🎯 Primary Goals
|
||||
1. **V3 API Verification:** Ensure all CRUD operations utilize the generic `api_crud` endpoints (Verified).
|
||||
2. **Quick Add UI:** Implement a specialized interface for rapid, friction-free entry creation.
|
||||
3. **Append/Prepend UI:** Allow users to quickly add text to the beginning or end of existing entries without full edit mode.
|
||||
4. **Interop & Portability:** Robust import/export logic for Markdown/HTML (Nextcloud Notes compatibility).
|
||||
5. **Security Hardening:** Review and harden client-side encryption logic (BLOCKED).
|
||||
|
||||
---
|
||||
|
||||
## 3. Technical Architecture
|
||||
|
||||
### Backend (Completed)
|
||||
* **Router:** `api_crud` (Generic)
|
||||
* **Definitions:** `app/ae_obj_types_def.py` -> `app/object_definitions/journals.py`
|
||||
* **Endpoints:** `/v3/crud/journal/...` and `/v3/crud/journal_entry/...`
|
||||
|
||||
### Frontend (Completed UI modernization scope)
|
||||
* **State Management:** `src/lib/ae_journals/ae_journals_stores.ts`
|
||||
* **Local Storage:** Dexie.js (`db_journals`)
|
||||
* **API Client:** `src/lib/api/api.ts` -> `get_ae_obj`
|
||||
* **Export Engine:** Centralized templates in `src/lib/ae_journals/ae_journals_export_templates.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Feature Specifications
|
||||
|
||||
### ⚡ Quick Add (Complete)
|
||||
* **Component:** `src/routes/journals/ae_comp__journal_entry_quick_add.svelte`
|
||||
* **Behavior:** Creates a new `journal_entry` attached to the active journal without leaving the list view.
|
||||
|
||||
### 📝 Append / Prepend (Complete)
|
||||
* **Interaction:** Fast text injection via `AeCompModalJournalEntryAppend`.
|
||||
* **Logic:** Updates entry content without full editor state overhead.
|
||||
|
||||
### 🔄 Interop (Markdown/HTML) (Complete)
|
||||
* **Goal:** Bulk export/import for data portability.
|
||||
* **Templates:** Standard, Personal Log, Amazon Vine.
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Plan
|
||||
|
||||
### Phase 1: Foundation (Done)
|
||||
- [x] Backend cleanup (remove legacy routers).
|
||||
- [x] Verify frontend uses V3 API (`ae_journals__journal.ts`).
|
||||
|
||||
### Phase 2: Rapid Entry (Complete)
|
||||
- [x] Create `ae_comp__journal_entry_quick_add.svelte`.
|
||||
- [x] Integrate Quick Add into `+page.svelte`.
|
||||
|
||||
### Phase 3: Content Manipulation & Portability (Complete)
|
||||
- [x] Implement Append/Prepend logic.
|
||||
- [x] Implement Bulk Export/Import system.
|
||||
- [x] Establish centralized Export Template engine.
|
||||
|
||||
### Phase 4: Polish & Security (UI scope complete; security follow-ups transferred)
|
||||
- [x] Implement Auto-Save toggle and visual status indicators.
|
||||
- [x] Extract decryption workflow to non-reactive helper.
|
||||
- [x] **Standardize Configuration Modals:** Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
|
||||
- [x] **Journal Entry Config cleanup:** Summary now lives in Metadata; Alert lives in its own Alerts & Messaging section; Privacy Flags is visibility-only; Admin controls are split out and gated to trusted-access and above.
|
||||
- [x] **Shared Flags widget:** `AE_Object_Flags` now shows visible button text and hover titles instead of icon-only controls.
|
||||
- [x] **Modal sizing:** Entry config modal now expands to viewport height instead of stopping at a fixed 60vh body cap.
|
||||
- [x] **Delete/Remove behavior:** Entry config Admin section now uses the real delete helper. Managers/admins see Delete (hard delete); trusted access sees Remove (disable semantics).
|
||||
- [x] **RESOLVED:** Decryption workflow stability (Fixed via dependency isolation).
|
||||
- [x] **Style Standardization (2026-03-06):** Full Skeleton v4 `preset-*` class pass across all 17 journal components. See style token table in Lessons Learned below.
|
||||
- [x] **Dark mode fixes:** Entry content hover, journal view section/description background and text colors.
|
||||
- [x] **Modal close button:** All 3 config modals use `dismissable={false}` + explicit `<X>` button in header snippet for correct right-aligned placement.
|
||||
- [x] **Global select padding:** Added `padding-inline: 0.5rem` to `@layer base` in `app.css` (safe — utility `px-*` classes override it where intentional).
|
||||
- [ ] Solidify E2EE passcode system for Journals and Entries. See active task list.
|
||||
- [ ] Audit encryption flow for Quick Added and Imported entries. See active task list.
|
||||
- [ ] Integrate Outbound Email sharing. Deferred pending product confirmation.
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Lessons Learned: Solving the Svelte 5 Reactivity Hang
|
||||
|
||||
During the implementation of the Privacy/Decryption toggle and the new Configuration Modals, we encountered critical browser hangs caused by infinite reactivity loops. Here is how we resolved them:
|
||||
|
||||
### 1. Rigorous Dependency Isolation (`untrack`)
|
||||
Svelte 5 runes (`$effect`, `$derived`) automatically track **every** reactive variable read inside them.
|
||||
* **The Problem:** An effect would read `save_status` or `tmp_entry_obj.content` to decide if it should sync, but the act of syncing would update those same variables, re-triggering the effect.
|
||||
* **The Fix:** Wrap any "check-only" state or store reads in `untrack(() => ... )`. This allows the effect to use the value without becoming a dependency of it. This is **CRITICAL** when initializing local state from props inside an effect.
|
||||
|
||||
### 2. Standardized Modal UI ("Aether Orange") & Style Token Conventions
|
||||
We have established a unified design language for configuration interfaces and all Journals UI. **Use these as the module template.**
|
||||
|
||||
#### Modal Chrome
|
||||
* **Header/Footer:** `bg-orange-100 dark:bg-orange-900` with consistent orange borders.
|
||||
* **Close button:** Always use `dismissable={false}` on the `<Modal>` and add an explicit `<button>` with `<X>` inside the `{#snippet header()}` so placement is fully in our control. The `flex-1` class on the `<h3>` pushes it right.
|
||||
* **Tabs:** Center-aligned `btn btn-sm` with `preset-filled-primary` (active) / `preset-tonal-surface` (inactive).
|
||||
* **Icons:** Every tab and primary action should have a Lucide icon for better scannability.
|
||||
* **Button titles:** Any button that uses icon+text or icon-only must include a descriptive `title` for hover clarity.
|
||||
* **Explicit Persistence:** Follow "Edit working copy → Save Changes" pattern to prevent accidental store/API churn.
|
||||
|
||||
#### Skeleton v4 Style Token Reference (Journals = canonical example)
|
||||
| Intent | Class |
|
||||
|---|---|
|
||||
| Primary CTA (save, create, open) | `btn preset-filled-primary` |
|
||||
| Neutral / cancel / close | `btn preset-tonal-surface` |
|
||||
| Secondary action | `btn preset-tonal-secondary` |
|
||||
| Success (confirmed save) | `btn preset-filled-success` |
|
||||
| Warning (caution action) | `btn preset-tonal-warning hover:preset-filled-warning-500` |
|
||||
| Error / danger (delete, force reset) | `btn preset-tonal-error hover:preset-filled-error-500` |
|
||||
| Warning action (remove/disable) | `btn preset-tonal-warning hover:preset-filled-warning-500` |
|
||||
| Active tab | `preset-filled-primary` |
|
||||
| Inactive tab | `preset-tonal-surface` |
|
||||
| Icon button | `btn-icon btn-icon-sm preset-tonal-surface` |
|
||||
| Input (base) | `input` |
|
||||
| Input (small) | `input input-sm` |
|
||||
| Select (base) | `select` |
|
||||
| Select (small) | `select select-sm` |
|
||||
| Textarea | `textarea` |
|
||||
| Badge (info/neutral) | `badge preset-tonal-surface` |
|
||||
| Badge (success) | `badge preset-tonal-success` |
|
||||
| Badge (error) | `badge preset-tonal-error` |
|
||||
|
||||
#### Removed Patterns (never use)
|
||||
- All `variant-*` classes are now fully removed from the codebase. Use only `preset-*` classes for all buttons and interactive elements.
|
||||
- `variant-form-material` — Skeleton v2, removed from all inputs/selects/textareas
|
||||
- `input-bordered` — non-standard, removed
|
||||
- DaisyUI `modal` / `modal-box` / `modal-action` wrapper divs inside Flowbite `<Modal>` — removed
|
||||
|
||||
#### Dark Mode Rules
|
||||
- Any `bg-{color}-100` dynamic background **must** have a `dark:bg-gray-800` (or similar) override — light shades are unreadable in dark mode.
|
||||
- Hover states on content areas need both light and dark variants: `hover:bg-blue-100 dark:hover:bg-blue-950`.
|
||||
- Text locked to `dark:text-gray-900` is almost always wrong — use `dark:text-gray-100`.
|
||||
|
||||
### 3. Dexie LiveQuery Subscriptions
|
||||
* **The Problem:** Accessing `liveQuery` observables directly in templates results in `[object Object]` or `undefined` property errors.
|
||||
* **The Mandate:** ALWAYS use the `$` prefix (e.g. `$lq__obj`) when passing or using data from a Dexie `liveQuery`.
|
||||
|
||||
### 4. Manual Deep Copying vs. Proxies
|
||||
Svelte 5 state is backed by Proxies.
|
||||
* **The Problem:** Using `JSON.parse(JSON.stringify(proxy))` can sometimes trigger unexpected behavior or loops when used inside a reactive context.
|
||||
* **The Fix:** Implement a manual `deep_copy` helper or selective property assignment when syncing "Original" vs "Temporary" state. This ensures `orig_entry_obj` is a plain JS object, making the `has_unsaved_changes` check stable.
|
||||
|
||||
### 5. Journal Entry Config Layout Notes
|
||||
The Entry Config modal now follows a stricter section grammar:
|
||||
* `Metadata` contains category, tags, summary, archive date, and template.
|
||||
* `Status & Security` contains enabled/hidden/priority/sort.
|
||||
* `Visibility & Audience` contains only visibility/audience toggles.
|
||||
* `Alerts & Messaging` contains alert flag + alert message.
|
||||
* `Admin` is gated to trusted access and above, and is the only place for notes plus delete/remove actions.
|
||||
|
||||
### 3. Concurrency Locking (`is_processing`)
|
||||
* **The Problem:** Decryption (Async) and Auto-Save (Debounced Async) can fire nearly simultaneously.
|
||||
* **The Fix:** Use a simple `is_processing` boolean flag. If any async workflow is active, block others from starting and prevent the `has_unsaved_changes` derived rune from reporting `true`.
|
||||
|
||||
### 4. Comparison Normalization
|
||||
* **The Problem:** Trivial differences (e.g., `null` vs `""` or trailing whitespace) would trigger "unsaved changes" and fire the save loop.
|
||||
* **The Fix:** Use a `normalize()` function in the `has_unsaved_changes` derived rune to trim strings and treat `null/undefined` as empty strings during comparison.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Technical Blocker: The "Decryption-Sync" Loop (Resolved)
|
||||
|
||||
### The Issue
|
||||
The component is suffering from a **Reactive Feedback Loop** between decryption, auto-save, and background IDB refreshes.
|
||||
1. **Decrypting content** triggers a change in `tmp_entry_obj.content`.
|
||||
2. The **Auto-Save effect** sees this as a manual user edit and saves to the database.
|
||||
3. **Dexie LiveQuery** detects the DB update and refreshes the object.
|
||||
4. The **Sync effect** resets the entry to its encrypted state (from DB).
|
||||
5. The **Auto-Decryption effect** fires again, starting the loop over.
|
||||
|
||||
### What we tried:
|
||||
* **`is_processing` flags:** Attempted to block reactivity during decryption.
|
||||
* **`untrack()`:** Attempted to isolate store updates.
|
||||
* **Reference Sync:** Attempted to update `orig_entry_obj` simultaneously with decryption to fool the change detector.
|
||||
* **Direct Store Updates:** Switching from property assignment to `journals_sess.update()` to fix Svelte 5 notification failures.
|
||||
|
||||
### Future Fix Ideas:
|
||||
1. **Native Svelte 5 State:** Refactor `journals_sess` from a Svelte 4 `Writable` to a class using `$state`.
|
||||
2. **Logic Extraction:** Move decryption logic into a non-reactive class/helper to isolate side effects from the UI render cycle.
|
||||
3. **Hash Comparison:** Use content hashes for change detection instead of string comparisons to avoid whitespace/normalization loops.
|
||||
Reference in New Issue
Block a user