3 Commits

Author SHA1 Message Date
Scott Idem
4a39ca1468 refactor(sort): introduce build_tmp_sort utility; apply to event_presentation and journals
Creates src/lib/ae_core/core__idb_sort.ts with build_tmp_sort() — a shared
helper for computing tmp_sort_1/2/3 fields stored in Dexie. Fixes two bugs
present in all generic _process_generic_props implementations:
  - priority encoded as 0/1 ASC (true sorted last); now inverted: true→'0'
  - sort stored as unpadded string ("10" < "2"); now 8-char zero-padded

Applied to:
  - ae_events__event_presentation: replaces inline specific_processor code
  - ae_journals__journal + ae_journals__journal_entry: replaces manual formulas;
    journal liveQueries (.reverse().sortBy()) updated to plain .sortBy() since
    the inverted encoding handles direction without needing Dexie's .reverse()

Other modules (sessions, presenters, locations, posts, core) left unchanged
until their sort behavior is reviewed separately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 20:38:25 -04:00
Scott Idem
182a066d38 fix(pres_mgmt): use tmp_sort_2 for presentation sort in Pres Mgmt and Launcher
Compute presentation-specific tmp_sort_1/tmp_sort_2 in specific_processor,
overriding the generic values from _process_generic_props which had two bugs:
- priority encoded as 0/1 ASC (backwards — true should sort first)
- sort stored as unpadded string ("10" < "2" lexicographically)
- start_datetime and code not included (presentation-specific fields)

New encoding: priority(inv)_sort(8-padded)_start_datetime_code[_name]
Both liveQueries (Pres Mgmt session page, Launcher session view) now use
.sortBy('tmp_sort_2') — cleaner and uses the indexed field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 20:20:50 -04:00
Scott Idem
35fed53e2a fix(launcher): sort presentations by priority > sort > start_datetime > code > name
Replaced single-field sortBy() (poster→name, oral→start_datetime) with
toArray() + JS comparator matching the same sort chain as Pres Mgmt.
Removes the sort_by branch since the comparator handles both session types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 20:11:14 -04:00
8 changed files with 98 additions and 46 deletions

View File

@@ -0,0 +1,54 @@
/**
* src/lib/ae_core/core__idb_sort.ts
*
* Shared utility for computing tmp_sort_* fields stored in Dexie.
* All fields are designed for ascending .sortBy() — no .reverse() needed.
*
* Encoding rules:
* priority — inverted boolean: true→'0', false→'1' so priority=true sorts first (ASC)
* sort — zero-padded integer string so "00000010" < "00000020" (correct numeric order)
* all other fields — appended as-is; ISO 8601 datetimes already sort correctly
*
* Usage:
* const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
* prefix: [obj.group ?? '0'], // fields before priority (optional)
* priority: obj.priority,
* sort: obj.sort,
* fields_1: [obj.start_datetime], // appended to base for tmp_sort_1
* fields_2: [obj.name], // appended after fields_1 for tmp_sort_2
* fields_3: [obj.updated_on], // appended after fields_2 for tmp_sort_3
* });
*/
export function build_tmp_sort({
prefix = [],
priority,
sort,
fields_1 = [],
fields_2 = [],
fields_3 = [],
pad_width = 8
}: {
prefix?: (string | null | undefined)[];
priority?: boolean | null;
sort?: number | string | null;
fields_1?: (string | null | undefined)[];
fields_2?: (string | null | undefined)[];
fields_3?: (string | null | undefined)[];
pad_width?: number;
}): { tmp_sort_1: string; tmp_sort_2: string; tmp_sort_3: string } {
const clean = (v: string | null | undefined): string => v ?? '';
const p = priority ? '0' : '1';
const s = String(Number(sort ?? 0)).padStart(pad_width, '0');
const parts_base = [...prefix.map(clean), p, s].join('_');
const parts_1 = fields_1.map(clean).filter(Boolean).join('_');
const parts_2 = fields_2.map(clean).filter(Boolean).join('_');
const parts_3 = fields_3.map(clean).filter(Boolean).join('_');
const tmp_sort_1 = [parts_base, parts_1].filter(Boolean).join('_');
const tmp_sort_2 = [tmp_sort_1, parts_2].filter(Boolean).join('_');
const tmp_sort_3 = [tmp_sort_2, parts_3].filter(Boolean).join('_');
return { tmp_sort_1, tmp_sort_2, tmp_sort_3 };
}

View File

@@ -2,6 +2,7 @@ import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
import { db_events } from '$lib/ae_events/db_events';
import type { ae_EventPresentation } from '$lib/types/ae_types';
@@ -680,6 +681,17 @@ export async function process_ae_obj__event_presentation_props({
if (obj.event_session_id_random)
obj.event_session_id = obj.event_session_id_random;
if (obj.event_id_random) obj.event_id = obj.event_id_random;
// Override generic tmp_sort_* with presentation-specific encoding via
// build_tmp_sort. Order: priority DESC → sort ASC → start_datetime ASC → code ASC → name ASC
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
priority: obj.priority,
sort: obj.sort,
fields_1: [obj.start_datetime, obj.code],
fields_2: [obj.name]
});
obj.tmp_sort_1 = tmp_sort_1;
obj.tmp_sort_2 = tmp_sort_2;
return obj;
}
});

View File

@@ -4,6 +4,7 @@ import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
import { db_journals } from '$lib/ae_journals/db_journals';
import type { ae_Journal } from '$lib/types/ae_types';
@@ -885,9 +886,16 @@ export async function process_ae_obj__journal_props({
const updated =
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
obj.tmp_sort_3 = `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${obj.sort ?? '0'}_${
obj.name
}_${updated}`;
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
prefix: [obj.group ?? '0'],
priority: obj.priority,
sort: obj.sort,
fields_2: [obj.name],
fields_3: [updated]
});
obj.tmp_sort_1 = tmp_sort_1;
obj.tmp_sort_2 = tmp_sort_2;
obj.tmp_sort_3 = tmp_sort_3;
obj.combined_passcode = `${obj.passcode ?? ''}:${obj.private_passcode ?? ''}`;
return obj;

View File

@@ -4,6 +4,7 @@ import type { key_val } from '$lib/stores/ae_stores';
import { api } from '$lib/api/api';
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
import { db_journals } from '$lib/ae_journals/db_journals';
import type { ae_JournalEntry } from '$lib/types/ae_types';
@@ -1050,19 +1051,20 @@ export async function process_ae_obj__journal_entry_props({
obj.history = history;
obj.history_md_html = history_md_html;
// Journal entry-specific computed sort fields, overriding generic ones if needed
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
// Journal entry-specific computed sort fields via build_tmp_sort.
// Order: priority DESC → sort ASC → name ASC → updated ASC (all ascending, no .reverse())
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.name ?? ''}_${updated}`;
obj.tmp_sort_3 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
sort_val
}_${obj.name ?? ''}_${updated}`;
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
prefix: [obj.group ?? ''],
priority: obj.priority,
sort: obj.sort,
fields_2: [obj.name],
fields_3: [updated]
});
obj.tmp_sort_1 = tmp_sort_1;
obj.tmp_sort_2 = tmp_sort_2;
obj.tmp_sort_3 = tmp_sort_3;
return obj;
}

View File

@@ -90,11 +90,11 @@ let lq__event_file_obj_li = $derived.by(() => {
});
// Event Presentation
// WHY: $derived.by — same reason as above. Captures both id and sort_by in the outer
// closure so a new Observable is created whenever the session or its type changes.
// WHY: $derived.by — captures id in the outer closure so a new Observable is
// created whenever the session changes. tmp_sort_2 encodes the full sort chain:
// priority DESC → sort ASC → start_datetime ASC → code ASC → name ASC
let lq__event_presentation_obj_li = $derived.by(() => {
const id = slct__event_session_id;
const sort_by = type_code == 'poster' ? 'name' : 'start_datetime';
return liveQuery(async () => {
if (!id) return [];
@@ -105,7 +105,7 @@ let lq__event_presentation_obj_li = $derived.by(() => {
return await db_events.presentation
.where('event_session_id')
.equals(id)
.sortBy(sort_by);
.sortBy('tmp_sort_2');
});
});

View File

@@ -67,40 +67,18 @@ let lq__event_session_obj = $derived(
);
// 2. Presentation List Observable
// WHY: Dexie sortBy() is single-field only. Multi-field sort requires toArray() + JS comparator.
// Desired order: priority DESC → sort ASC → start_datetime ASC → code ASC → name ASC
// WHY: tmp_sort_2 encodes priority DESC → sort ASC → start_datetime ASC → code ASC → name ASC
// as a lexicographically correct string. See process_ae_obj__event_presentation_props.
let lq__event_presentation_obj_li = $derived(
liveQuery(async () => {
if (log_lvl)
console.log(
`[LQ] Querying Presentations for Session: ${url_session_id}`
);
const results = await db_events.presentation
return await db_events.presentation
.where('event_session_id')
.equals(url_session_id)
.toArray();
return results.sort((a, b) => {
// priority DESC (true first)
const pa = a.priority ? 1 : 0;
const pb = b.priority ? 1 : 0;
if (pb !== pa) return pb - pa;
// sort ASC
const sa = a.sort ?? 0;
const sb = b.sort ?? 0;
if (sa !== sb) return sa - sb;
// start_datetime ASC
const da = a.start_datetime ?? '';
const db_val = b.start_datetime ?? '';
if (da !== db_val) return da < db_val ? -1 : 1;
// code ASC
const ca = a.code ?? '';
const cb = b.code ?? '';
if (ca !== cb) return ca < cb ? -1 : 1;
// name ASC
const na = a.name ?? '';
const nb = b.name ?? '';
return na < nb ? -1 : na > nb ? 1 : 0;
});
.sortBy('tmp_sort_2');
})
);

View File

@@ -59,7 +59,6 @@ let lq__journal_obj_li = $derived(
return await db_journals.journal
.where('person_id')
.equals($ae_loc.person_id)
.reverse()
.sortBy('tmp_sort_3');
})
);

View File

@@ -105,7 +105,6 @@ let lq__journal_obj_li = $derived(
let results = await db_journals.journal
.where('person_id')
.equals($ae_loc.person_id)
.reverse()
.sortBy('tmp_sort_2');
// Check if results are different than the current session version stored under $journals_slct