Every recipe list endpoint wrapped its result in
`JSON.parse(JSON.stringify(...))` before handing it to `json()`, which
then serialises again — a full extra stringify+parse cycle per response.
`lean()` already returns plain objects and ObjectIds/Dates serialise
correctly through `json()`'s single `JSON.stringify`, so the extra round
trip was pure waste.
Removed from the 9 output-side call sites (all_brief, category,
category/[cat], tag, tag/[tag], icon, icon/[icon], in_season/[month],
search, favorites/recipes, offline-db, translate/untranslated).
Kept the two deep-clone-before-mutation usages in items/[name] and
json-ld/[name] — those are load-bearing.
Shuffle stays server-side: moving it to the client would need a hero
preload + hydration rework that's bigger than a perf tweak.
Chart.js (~244 KB) was a top-level import, so every route that referenced
FitnessChart.svelte transitively pulled it. Defer it to an async block
inside onMount so non-stats fitness routes (workout, check-in, nutrition,
history list) no longer ship chart.js.
- `ChartCtor` holds the async-loaded constructor
- `disposed` guard handles unmount during the import
- theme MutationObserver / matchMedia wiring moved inside the async
block so it only attaches once the chart actually exists
Barrel `from '@lucide/svelte'` imports pulled every referenced icon into
one shared 748 KB client chunk. Switch every call site to per-icon
subpaths (`@lucide/svelte/icons/<kebab-name>`) so Vite tree-shakes each
icon independently. Also logs the TODO list for the perf audit so we
don't lose track.
- 46 files, 106 unique icons
- single `Minus as MinusIcon` alias preserved
- Lucide-internal aliases (`AlertTriangle`, `BarChart3`) resolve through
Lucide's own re-export shims; no behavioral change
Body-measurement variation of <4 cm used to stretch the full chart
height, making normal weekly noise look dramatic. Now the y-axis
enforces a 4 cm floor centered on the data's midpoint; wider swings
render at their actual range as before.
- FitnessChart: new optional `yMin` / `yMax` props mapped to Chart.js
`suggestedMin` / `suggestedMax` — soft bounds, so data that exceeds
them still widens the axis.
- `/fitness/stats/history/[part]`: computes min/max across available
values (both sides if paired), enforces the 4 cm floor, passes to
FitnessChart. Tick distance stays on Chart.js auto — small ranges
get 0.5 cm ticks, wider ones scale up naturally.
Route slugs and nav label rename only — storage, API endpoints
(`/api/fitness/measurements`), and the `BodyMeasurement` Mongo model
keep their technical names.
- `/fitness/measure` → `/fitness/check-in` (EN)
- `/fitness/messen` → `/fitness/erfassung` (DE)
- Folder `[measure=fitnessMeasure]` → `[checkin=fitnessCheckIn]`
(git rename; history preserved).
- Param matcher `fitnessMeasure.ts` → `fitnessCheckIn.ts`, accepts
`check-in` / `erfassung`.
- `fitnessSlugs(lang).measure` and `fitnessLabels(lang).measure` code
keys are unchanged — value returns "check-in"/"erfassung" and
"Check-in"/"Erfassung" respectively, so no call site needs touching.
- slugMap language-detection updated to `erfassung ↔ check-in`.
- Service-worker cache list + the layout regex that gates the wider
content width now reference the new slugs.
- Nav icon swapped from `Ruler` to `NotebookPen` — reads as "logging
entries" and spans weight / composition / period better.
Bookmarks on the old URLs will 404; no redirect added.
Mirrors the weight chart pipeline (SMA + ±1σ confidence band) for
body-fat %, but emits deltas from the first displayed measurement so
the y-axis shows change instead of raw numbers. Title surfaces the
baseline (e.g. "Body Fat · Δ from 18.2%"), y-unit is "pp" (percentage
points), colours are purple trend on top of an orange raw-data line
so it reads differently from weight's green+blue at a glance.
FitnessChart gained two shared upgrades: `interaction.mode = 'index'`
on line charts so hovering the x-axis shows tooltips for every dataset
(including the trend line whose pointRadius is 0), and a `σ` dataset
filter so the confidence band doesn't clutter the tooltip. A new
optional `tooltipFormatter` prop lets callers format the hover label;
the BF chart uses it to show the signed delta + reconstructed
absolute % for raw points and additionally the ±1σ window for trend
points (e.g. "+0.30 ±0.45 pp · 18.5% (18.0–18.9%)").
- New "Same as last" pill below each step's stepper. Clicking fills
the input(s) with the prior recorded value(s) — for paired steps
in split mode, both L and R — and advances to the next step.
Only rendered when a previous measurement exists; the placeholder
already surfaces the exact number so the button text stays terse.
- Copy L→R button resized to match the same-as-last pill (0.88 rem
text, 0.55 × 1.1 rem padding) and given top margin. Unicode →
swapped for a proper ArrowRight icon between L and R.
- i18n: added `same_as_last` and split `copy_l_to_r` into
`copy_l_to_r_before` / `copy_l_to_r_after` so each language keeps
its natural wrapping around the arrow (EN "Copy L / R",
DE "L / R übernehmen").
- Server POST now upserts by (user, calendar day). Non-conflicting
fields merge silently; real overwrites (new non-empty value ≠
stored value) return 409 with the conflict list. Client retries
with `?overwrite=1` after a confirm dialog naming each field and
its old→new value. Null/empty payload fields are skipped, so
logging a body-fat entry on a day that already has weight merges
cleanly without flagging a phantom weight conflict.
- `summaryParts` in the history now includes a body-parts count,
e.g. "86 kg · 0.1% bf · 5 body parts" or "5 body parts" instead
of the flat "Body measurements only" fallback. Pluralised in EN
and DE.
- Inline quick-edit: "Full edit →" text replaced by a dashed primary
pill `Pencil · Edit all fields · ChevronRight`, inlined with the
X / ✓ action buttons on the same row. The label collapses to
icons only at ≤480px so the three controls stay on one line.
- Quick-edit date input swapped from native `<input type="date">`
to the site's `DatePicker` component.
- New i18n: `overwrite_title`, `overwrite_message`, `overwrite_confirm`.
- TODO.md marks features #2 and #3 done. CLAUDE.md carries a
policy note (no AI-attribution trailers on commits).
- Add SearchInput component for reusable search UI
- Add search functionality to prayers list with two-tier results:
- Primary matches (name/searchTerms) shown first
- Secondary matches (text content) shown after with reduced opacity
- Add individual prayer pages with language-appropriate slugs
(e.g., /glaube/gebete/ave-maria, /faith/prayers/hail-mary)
- Make prayer cards clickable to navigate to individual pages
- Fix language visibility for prayers without Latin (BruderKlaus, Joseph)
- Add Prayer wrapper to MichaelGebet for consistent styling
- Use CSS columns for masonry layout with dynamic reordering