perf(faith/calendar): trim yearDays, send pre-filtered feastDots
yearDays was a 365-entry array (one per day in the LY window) with
{iso, name, rank, color, seasonKey} on each — the client only needed
the color (for the needle pin on the currently-selected day; RingView
re-did the feast filter itself). Split into:
- yearDays: {iso, color} — unchanged count, but ~60% smaller per entry
(drops name, rank, seasonKey)
- feastDots: {iso, name, rank, color} — new, pre-filtered to
rank > ferial server-side (~150 entries instead of 365)
RingView's `feastDots` derivation shrinks to filtering out just the
currently-selected day, and `activeFeasts` filters `feastDots` by arc
bounds instead of re-scanning yearDays. needleDay's color lookup still
works with the trimmed YearDay.
Also collapses a stray `locals.session ?? (locals.session ?? …)` the
earlier #5 sweep introduced in both calendar page loaders.
This commit is contained in:
@@ -11,7 +11,7 @@ Order = impact. Font items + app.html preload intentionally skipped.
|
||||
- [x] 5. Replace redundant `locals.auth()` with `locals.session` across all routes (68 files, 107 sites — loaders, actions, API endpoints)
|
||||
- [x] 6. Stream fitness stats loader — muscleHeatmap, nutritionStats, periods, sharedPeriods now stream via `{#await}`. `stats` still awaited (too many chart $deriveds depend on it)
|
||||
- [x] 7. Muscle-heatmap endpoint — add projection + O(1) bucket math. Overview already had a projection; set-subfield narrowing was attempted but reverted (returned malformed sets). Timeseries cap not feasible: totals are lifetime-scoped.
|
||||
- [ ] 8. Calendar payload trim — drop `name` from `yearDays`, pre-filter `feastDots` server-side
|
||||
- [x] 8. Calendar payload trim — `yearDays` narrowed to `{iso, color}` (needle lookup only), new pre-filtered `feastDots` array carries feast-specific metadata. Also fixed a stray double `locals.session ?? (locals.session ?? …)` in both calendar page loaders.
|
||||
- [ ] 9. History sessions endpoint — slim exercise payload for list view
|
||||
- [ ] 10. `Cache-Control` headers on stable API endpoints (all_brief, calendar, exercises metadata)
|
||||
- [ ] 11. Search — debounce 200 ms + server-side pre-normalized `_searchKey`
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.46.21",
|
||||
"version": "1.46.22",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -16,14 +16,23 @@ export interface CalendarDay {
|
||||
rite1962?: Rite1962Detail;
|
||||
}
|
||||
|
||||
// Compact per-day shape returned for the full year so the ring / month-grid
|
||||
// overview views can render without refetching. Kept small on purpose.
|
||||
// Compact per-day shape returned for the full window of the liturgical year.
|
||||
// Kept to the bare minimum needed client-side: the ring needs a color for the
|
||||
// needle on the selected day (which may be a ferial with no rank metadata),
|
||||
// everything else goes through the separate `feastDots` array.
|
||||
export interface YearDay {
|
||||
iso: string;
|
||||
color: string; // primary color key (WHITE/RED/...)
|
||||
}
|
||||
|
||||
// Pre-filtered list of days that render a feast dot on the ring — rank > feria
|
||||
// — with the metadata the ring and side panel need for each. Sent alongside
|
||||
// YearDay so clients don't have to filter 365 entries themselves.
|
||||
export interface FeastDot {
|
||||
iso: string;
|
||||
name: string;
|
||||
rank: string;
|
||||
color: string; // primary color key (WHITE/RED/...)
|
||||
seasonKey: string | null;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface SeasonArc {
|
||||
|
||||
+21
-8
@@ -13,16 +13,17 @@ import {
|
||||
type Diocese1969,
|
||||
type Rite
|
||||
} from '../../../../calendarI18n';
|
||||
import { seasonColorFor } from '../../../../calendarColors';
|
||||
import { rankDotSize, seasonColorFor } from '../../../../calendarColors';
|
||||
import {
|
||||
getYear,
|
||||
getYear1962,
|
||||
isoFor
|
||||
} from '$lib/server/liturgicalCalendar';
|
||||
import type { CalendarDay, SeasonArc, YearDay } from '$lib/calendarTypes';
|
||||
import type { CalendarDay, FeastDot, SeasonArc, YearDay } from '$lib/calendarTypes';
|
||||
|
||||
export type {
|
||||
CalendarDay,
|
||||
FeastDot,
|
||||
ProperSection,
|
||||
Rite1962Commem,
|
||||
Rite1962Detail,
|
||||
@@ -234,14 +235,25 @@ export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const yearDays: YearDay[] = sortedYear.map((d, i) => ({
|
||||
// `yearDays` only carries what the ring's needle-color lookup needs for any
|
||||
// day (feast or ferial). Feast metadata (name, rank) moves into `feastDots`
|
||||
// below so the client can iterate it directly without filtering 365 entries.
|
||||
const yearDays: YearDay[] = sortedYear.map((d) => ({
|
||||
iso: d.iso,
|
||||
name: d.name,
|
||||
rank: d.rank,
|
||||
color: d.colorKeys[0] ?? 'GREEN',
|
||||
seasonKey: filledSeasons[i]
|
||||
color: d.colorKeys[0] ?? 'GREEN'
|
||||
}));
|
||||
|
||||
const feastDots: FeastDot[] = [];
|
||||
for (const d of sortedYear) {
|
||||
if (rankDotSize(d.rank) === 0) continue;
|
||||
feastDots.push({
|
||||
iso: d.iso,
|
||||
name: d.name,
|
||||
rank: d.rank,
|
||||
color: d.colorKeys[0] ?? 'GREEN'
|
||||
});
|
||||
}
|
||||
|
||||
const seasonArcs: SeasonArc[] = [];
|
||||
let cur: SeasonArc | null = null;
|
||||
for (let i = 0; i < sortedYear.length; i++) {
|
||||
@@ -282,6 +294,7 @@ export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
|
||||
month,
|
||||
monthDays,
|
||||
yearDays,
|
||||
feastDots,
|
||||
seasonArcs,
|
||||
windowStart,
|
||||
windowEnd,
|
||||
@@ -291,6 +304,6 @@ export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
|
||||
todayIso,
|
||||
selected: selectedEntry,
|
||||
selectedIso,
|
||||
session: locals.session ?? (locals.session ?? await locals.auth())
|
||||
session: locals.session ?? await locals.auth()
|
||||
};
|
||||
};
|
||||
|
||||
+2
@@ -28,6 +28,7 @@
|
||||
const month = $derived(data.month);
|
||||
const monthDays = $derived(data.monthDays);
|
||||
const yearDays = $derived(data.yearDays);
|
||||
const feastDots = $derived(data.feastDots);
|
||||
const seasonArcs = $derived(data.seasonArcs);
|
||||
const today = $derived(data.today);
|
||||
const todayIso = $derived(data.todayIso);
|
||||
@@ -267,6 +268,7 @@
|
||||
{year}
|
||||
{liturgicalYear}
|
||||
{yearDays}
|
||||
{feastDots}
|
||||
{seasonArcs}
|
||||
{todayIso}
|
||||
{selectedIso}
|
||||
|
||||
+9
-20
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { YearDay, SeasonArc } from './+page.server';
|
||||
import type { FeastDot, YearDay, SeasonArc } from './+page.server';
|
||||
import type { CalendarLang } from '../../../../calendarI18n';
|
||||
import { litBg, litInk, rankDotSize } from '../../../../calendarColors';
|
||||
import { Tween, prefersReducedMotion } from 'svelte/motion';
|
||||
@@ -11,6 +11,7 @@
|
||||
year,
|
||||
liturgicalYear,
|
||||
yearDays,
|
||||
feastDots: feastDotsProp,
|
||||
seasonArcs,
|
||||
todayIso,
|
||||
selectedIso = null,
|
||||
@@ -25,6 +26,7 @@
|
||||
year: number;
|
||||
liturgicalYear: number;
|
||||
yearDays: YearDay[];
|
||||
feastDots: FeastDot[];
|
||||
seasonArcs: SeasonArc[];
|
||||
todayIso: string;
|
||||
selectedIso?: string | null;
|
||||
@@ -161,20 +163,10 @@
|
||||
return out;
|
||||
});
|
||||
|
||||
// Feast dots: keep only the highest-ranking feast per ISO date, skip ferias.
|
||||
// The currently-selected feast is omitted because the static needle pin at
|
||||
// the top of the ring represents it.
|
||||
const feastDots = $derived.by(() => {
|
||||
const byDate = new Map<string, YearDay>();
|
||||
for (const d of yearDays) {
|
||||
const size = rankDotSize(d.rank);
|
||||
if (size === 0) continue;
|
||||
if (d.iso === needleIso) continue;
|
||||
const cur = byDate.get(d.iso);
|
||||
if (!cur || rankDotSize(d.rank) > rankDotSize(cur.rank)) byDate.set(d.iso, d);
|
||||
}
|
||||
return [...byDate.values()];
|
||||
});
|
||||
// Feast dots come pre-filtered from the server (rank > ferial, one per ISO).
|
||||
// Only strip the currently-selected day here since the needle pin at the top
|
||||
// already represents it.
|
||||
const feastDots = $derived(feastDotsProp.filter((d) => d.iso !== needleIso));
|
||||
|
||||
// A season can split into multiple arcs within one gregorian year (e.g.
|
||||
// ChristmasTide spans both Dec 25–31 and Jan 1–13 of the civil year). Each
|
||||
@@ -206,11 +198,8 @@
|
||||
);
|
||||
|
||||
const activeFeasts = $derived.by(() => {
|
||||
if (!active) return [] as YearDay[];
|
||||
return yearDays.filter(
|
||||
(d) =>
|
||||
rankDotSize(d.rank) > 0 && d.iso >= active.start && d.iso <= active.end
|
||||
);
|
||||
if (!active) return [] as FeastDot[];
|
||||
return feastDotsProp.filter((d) => d.iso >= active.start && d.iso <= active.end);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
|
||||
+1
-1
@@ -96,6 +96,6 @@ export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
|
||||
iso,
|
||||
todayIso,
|
||||
day1: entry,
|
||||
session: locals.session ?? (locals.session ?? await locals.auth())
|
||||
session: locals.session ?? await locals.auth()
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user