fix(launcher): resolve hydration errors and missing file counts
- Initialized event_session_id to null in store template to prevent Svelte 5 binding errors.\n- Fixed invalid bind expressions in Launcher layout.\n- Corrected view: alt propagation in session list background refresh to ensure file counts are retrieved.\n- Hardened duplicate launch protection with verify_hash enabled by default.\n- Updated SVELTE_DEXIE_GUIDE.md with binding pitfall documentation.
This commit is contained in:
@@ -92,6 +92,35 @@ export function createLiveQueryStore<T>(query: () => T | Promise<T>) {
|
|||||||
|
|
||||||
The `createLiveQueryStore` function creates a readable store that automatically updates whenever the data in the `friends` table changes. The `$friends` variable in the component will always contain the latest data from the database.
|
The `createLiveQueryStore` function creates a readable store that automatically updates whenever the data in the `friends` table changes. The `$friends` variable in the component will always contain the latest data from the database.
|
||||||
|
|
||||||
|
## Svelte 5 Binding Pitfalls
|
||||||
|
|
||||||
|
### 1. `props_invalid_value` (The "Expression Binding" Error)
|
||||||
|
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.
|
||||||
|
```svelte
|
||||||
|
<!-- Error: Can only bind to an Identifier or MemberExpression -->
|
||||||
|
<Launcher_menu bind:slct__event_session_id={$events_slct.event_session_id || null} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Correct Pattern:**
|
||||||
|
Ensure the source value is already normalized before binding, or use a reactive effect to handle the fallback.
|
||||||
|
```typescript
|
||||||
|
// Normalize in an effect or derivation
|
||||||
|
$effect(() => {
|
||||||
|
if ($events_slct.event_session_id === undefined) {
|
||||||
|
$events_slct.event_session_id = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
```svelte
|
||||||
|
<!-- Bind directly to the normalized property -->
|
||||||
|
<Launcher_menu bind:slct__event_session_id={$events_slct.event_session_id} />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Safe Data Processing for IndexedDB Sorting
|
## Safe Data Processing for IndexedDB Sorting
|
||||||
|
|
||||||
When preparing data for IndexedDB, especially when creating composite sort keys, it is critical to handle `null` or `undefined` values safely to prevent runtime crashes that can interrupt the data synchronization process.
|
When preparing data for IndexedDB, especially when creating composite sort keys, it is critical to handle `null` or `undefined` values safely to prevent runtime crashes that can interrupt the data synchronization process.
|
||||||
|
|||||||
@@ -196,7 +196,12 @@ export async function load_ae_obj_li__event_session({
|
|||||||
if (log_lvl) console.log(`✅ [Trace] load_ae_obj_li: CACHE HIT at ${elapsed}ms (${cached_li.length} items).`);
|
if (log_lvl) console.log(`✅ [Trace] load_ae_obj_li: CACHE HIT at ${elapsed}ms (${cached_li.length} items).`);
|
||||||
|
|
||||||
// Background refresh (non-blocking)
|
// Background refresh (non-blocking)
|
||||||
_refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl: log_lvl > 1 ? log_lvl : 0 });
|
_refresh_session_li_background({
|
||||||
|
api_cfg, for_obj_type, for_obj_id, view,
|
||||||
|
inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li,
|
||||||
|
enabled, hidden, limit, offset, order_by_li, try_cache,
|
||||||
|
log_lvl: log_lvl > 1 ? log_lvl : 0
|
||||||
|
});
|
||||||
|
|
||||||
return cached_li;
|
return cached_li;
|
||||||
} else if (log_lvl) {
|
} else if (log_lvl) {
|
||||||
@@ -207,15 +212,15 @@ export async function load_ae_obj_li__event_session({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl });
|
return await _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, view, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl }: any) {
|
async function _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_id, view, inc_file_li, inc_all_file_li, inc_presentation_li, inc_presenter_li, enabled, hidden, limit, offset, order_by_li, try_cache, log_lvl }: any) {
|
||||||
const start_time = performance.now();
|
const start_time = performance.now();
|
||||||
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
|
if (typeof navigator !== 'undefined' && !navigator.onLine) return [];
|
||||||
try {
|
try {
|
||||||
if (log_lvl) console.log(`📡 [Trace] _refresh_session_li: API Fetching for=${for_obj_type}:${for_obj_id}`);
|
if (log_lvl) console.log(`📡 [Trace] _refresh_session_li: API Fetching for=${for_obj_type}:${for_obj_id} (view=${view})`);
|
||||||
const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_session', for_obj_type, for_obj_id, enabled, hidden, limit, offset, order_by_li, log_lvl });
|
const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_session', for_obj_type, for_obj_id, view, enabled, hidden, limit, offset, order_by_li, log_lvl });
|
||||||
|
|
||||||
if (result_li) {
|
if (result_li) {
|
||||||
const processed = await process_ae_obj__event_session_props({ obj_li: result_li, log_lvl });
|
const processed = await process_ae_obj__event_session_props({ obj_li: result_li, log_lvl });
|
||||||
|
|||||||
@@ -667,6 +667,7 @@ const events_slct_obj_template: key_val = {
|
|||||||
event_presenter_obj: {},
|
event_presenter_obj: {},
|
||||||
|
|
||||||
session_id: null,
|
session_id: null,
|
||||||
|
event_session_id: null,
|
||||||
session_obj: {},
|
session_obj: {},
|
||||||
session_obj_li: [],
|
session_obj_li: [],
|
||||||
event_session_obj: {},
|
event_session_obj: {},
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export async function load({ params, parent, url }) {
|
|||||||
inc_presenter_li: true,
|
inc_presenter_li: true,
|
||||||
enabled: 'enabled',
|
enabled: 'enabled',
|
||||||
hidden: 'all',
|
hidden: 'all',
|
||||||
|
view: 'alt', // Standardized View for file counts and extended metadata
|
||||||
limit: 150,
|
limit: 150,
|
||||||
log_lvl: 0
|
log_lvl: 0
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,6 +92,7 @@
|
|||||||
|
|
||||||
async function handle_open_file() {
|
async function handle_open_file() {
|
||||||
if (log_lvl) console.log('*** handle_open_file() ***');
|
if (log_lvl) console.log('*** handle_open_file() ***');
|
||||||
|
if (open_file_clicked) return; // Hard Guard: Already processing
|
||||||
|
|
||||||
$events_slct.event_file_id = event_file_id;
|
$events_slct.event_file_id = event_file_id;
|
||||||
$events_slct.event_file_obj = event_file_obj;
|
$events_slct.event_file_obj = event_file_obj;
|
||||||
@@ -107,7 +108,8 @@
|
|||||||
|
|
||||||
const exists = await native.check_hash_file_cache({
|
const exists = await native.check_hash_file_cache({
|
||||||
cache_root,
|
cache_root,
|
||||||
hash: event_file_obj.hash_sha256
|
hash: event_file_obj.hash_sha256,
|
||||||
|
verify_hash: true // Hardened: Trust No One!
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
interface Props {
|
interface Props {
|
||||||
slct__event_session_id?: string;
|
slct__event_session_id?: string | null;
|
||||||
lq__event_session_obj: any;
|
lq__event_session_obj: any;
|
||||||
type_code?: string;
|
type_code?: string;
|
||||||
log_lvl?: number;
|
log_lvl?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
slct__event_session_id = $bindable(''),
|
slct__event_session_id = $bindable(null),
|
||||||
lq__event_session_obj,
|
lq__event_session_obj,
|
||||||
type_code = $bindable(''),
|
type_code = $bindable(''),
|
||||||
log_lvl = $bindable(1)
|
log_lvl = $bindable(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user