Commit Graph
24 Commits
Author SHA1 Message Date
Marc-André Bombeck 86d80d2c04 refactor(admin): migrate section cards onto the shared card primitives
The admin tree hand-rolled its card surfaces and stepped padding at sm:
instead of md:, so it read denser than every other route on a tablet.
Move the sections onto SettingsCard/SettingsCardHeader and the mini-rows
onto ListRow; no behaviour, data, or copy changes.
2026-07-01 23:44:55 +02:00
Marc-André Bombeck c38271bc44 fix(a11y): distinguish errors from empty states and close naming gaps
Add isError branches with a retry affordance to the mood, illness, and
vorsorge lists so a failed read no longer renders as "no entries yet"
and invites duplicate re-entry, matching the labs and cycle pattern.

Make the admin backups table reachable on a phone with a card fallback,
name the admin language and timezone selects and the AI raw-data switch
for screen readers, and replace the touch-unreachable title= help on the
glucose clinical panels with a tap-capable info popover.

Route the password-strength label through the AA-tuned semantic tokens,
underline the register login link so it is not distinguished by colour
alone, give the labs load error alert semantics, and explain why the
target editor Save button is disabled with aria-invalid and a live
alert.
2026-06-28 02:53:14 +02:00
Marc-André Bombeck 88c4a8428f fix(admin): harden the Danger-Zone wipe to match the Backups Restore gate
The global data wipe auto-supplied confirm:'DELETE ALL' from a single-click
dialog, leaving the server's typed-token defence dead and making the most
destructive action a weaker gate than the less-destructive restore. Mirror
RestoreRowDialog: require typing 'DELETE ALL' into an Input that arms the
destructive button, reset on close.

Also attach an Idempotency-Key header to the wipe so a double-submit cannot
re-run the transaction, add the documented post-requireAdmin()
'admin-data-delete:<userId>' rate-limit bucket alongside the anonymous IP
throttle, and switch BackupsSection off the non-reactive formatDateTime
onto the locale-reactive useFormatters() hook its siblings use.
2026-06-16 23:05:11 +02:00
Marc-André Bombeck 1e7b701fa6 fix(admin): give the Backups header room to breathe
The snapshot-count badge now leads (numbers first) and the explainer
plus docs link move into the card header's description slot, which
carries proper spacing and left-aligns under the title. Previously the
description sat in a separate paragraph crammed directly beneath the
"Backup now" button.
2026-06-16 21:32:12 +02:00
Marc-André Bombeck 40f7196c20 feat(medications,coach,admin,perf): era corrections, a truthful weekly status and one card language everywhere
The dashboard snapshot fires the moment login succeeds instead of
waiting for the page chunk (start ~670 to ~200 ms throttled). Guided
follow-ups are personalised from the user's snapshot and an answer gets
the coach's streamed reaction before the next question. Archived
schedule eras can be corrected through a superseding revision that
keeps the original as an audit trail, era writes serialise per
medication, and duplicate dose times collapse in validation. A rolling
medication whose next dose is tomorrow can no longer read as heavily
overdue — the card status derives strictly from the served next-due —
and the adherence bars keep a fixed geometry in every state. The
server-wide assistant key gains its admin card, the one duplicated
coach preference collapses onto a single owner, twenty-seven admin and
settings card headers move onto the shared pattern with sixty
containers normalised, and the remaining hydration warnings are gone.
2026-06-11 13:57:15 +02:00
Marc-André Bombeck 3b480b9885 perf(i18n,shell): ship one locale and stop blocking the first paint
The client bundle carries only the active locale — resolved on the
server and handed to the provider with no hydration flash, halving the
dashboard route to under a megabyte parsed (288 KiB gzip). The auth
shell renders the app chrome immediately instead of a spinner while the
session resolves in parallel, the widgets request folds into the
snapshot it duplicated, and insight pills prefetch their sub-pages —
a first visit to a sub-page lands in about ninety milliseconds instead
of nearly a second.
2026-06-11 02:19:27 +02:00
Marc-André Bombeck 024da9fe96 fix(ui): granular sleep sources, viewport-safe tour, one aligned settings language
A night with coarse apple-health samples next to granular watch data
showed only awake and asleep — the per-session source pick now prefers
the source that knows its stages. Tour popovers clamp into the
viewport, wrap their footer and fall back to a bottom sheet on narrow
screens. Settings and admin sections share one alignment rule — icon
column left, everything else flush on the content column — with neutral
header icons, no duplicated section titles under the shell heading, and
every select on the account-style idiom with a visible chevron. The
dashboard's customize icon matches insights, the target-settings link
leaves the home charts, and the mood heatmap adopts the damped fills
the other mood charts already use.
2026-06-10 21:19:05 +02:00
Marc-André Bombeck 04765ea8ac style: one spacing, title and skeleton language across the app
Icons inside buttons drop their manual margins in favour of the
button's built-in gap (two hundred call sites), form field stacks settle
on one spacing step, card titles on exactly two sizes, inline field
errors on one text size, and a dozen hand-rolled loading pulses move
onto the shared skeleton primitive while keeping their silhouettes. The
intake dialog wording joins the rest of the app on 'übersprungen', and
the UI guidelines now document the two deliberate footer and
touch-target systems.
2026-06-10 16:54:15 +02:00
Marc-André Bombeck 6f45d72a84 fix(ui): unclip the score-ring glow, reachable insight pills, consistent destructive actions
The score ring's bloom was clipped by the SVG's own viewport — the
drop-shadow sits on a child of the viewBox-filling arc — so the glow
never extended past the circle; the SVG now overflows visibly on every
host. The insight pill strip gains chevron buttons and wheel scrolling
so an overflowing row is reachable with a mouse. Safari favourites
rendered the transparent icon on a white plate; a flattened
apple-touch-icon and a maskable manifest icon fix both. The
time-of-day mood chart adopts the damped fill the weekday chart already
uses, eighteen delete confirmations move onto the destructive button
variant (one of them had the wrong text colour), five confirmation
dialogs gain pending states, the sources dialog stacks its primary
action on top on mobile, and the token form aligns input and button
heights.
2026-06-10 12:56:06 +02:00
Marc-André BombeckandClaude Opus 4.8 e984ada5f1 feat(cycle): import Apple Health reproductive data into cycle, add cycle export
Promote the reproductive HealthKit category types out of the
Apple-Health-import privacy-hold list and route them into cycle day-logs
instead of measurements, and surface cycle data on the three export
surfaces.

Import promotion:
- A new HealthKit -> cycle mapping module is the single source of truth for
  how MenstrualFlow, IntermenstrualBleeding, CervicalMucusQuality,
  OvulationTestResult, SexualActivity, PregnancyTestResult,
  ProgesteroneTestResult, Contraceptive and the symptom category types fold
  into CycleDayLog fields / seeded symptom keys. The XML importer
  accumulates same-day reproductive samples and writes one canonical
  day-log per day with source APPLE_HEALTH and a synthetic per-day
  externalId for idempotent re-import. The fold is gated on cycle tracking
  being enabled, so a non-cycle export never provisions cycle rows. A
  contraceptive method nudges the profile goal toward avoiding pregnancy
  only when the goal is still on its default. Basal body temperature stays
  a measurement and mirrors onto the day-log. Pregnancy/Lactation status
  and the pregnancy-bleeding types stay deferred; the cycle awareness types
  remain server-derived.

Export:
- FHIR emits LMP (LOINC 8665-2), average cycle length (64700-8), period
  length (64698-4) and the current phase as opt-in reproductive-health
  observations grouped into their own document section.
- The doctor PDF gains an opt-in cycle section (recent cycles, average
  length and variability, current phase), privacy-default off like mood.
- The full-backup export, the weekly backup worker and the off-host DR
  snapshot include the cycle tables, and the admin restore re-creates them;
  the day-log note round-trips as ciphertext and is never decrypted onto a
  report surface.

Mapping table documented in docs/api/cycle-healthkit-mapping.md. Adds unit
+ integration coverage for the mapping, the import routing, the FHIR
observations and the backup round-trip. OpenAPI regenerated. Six-locale
strings added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:16:54 +02:00
Marc-André Bombeck 6c7fc7dc84 chore(release): v1.4.45 — analytics 9 s perf fix + audit-marathon closure + v1.4.44 hotfix merged
Audit-marathon close: 14 waves + W10 QA + reconcile. Perf fix (analytics 9 s -> 2-3 s), Zod multi-issue rollout (41 routes), Withings parked-state automation, mobile-UI WCAG 2.5.5 close, QoL i18n + offline banner + account delete, security PII drop + replay-injection scan. Migration 0075 required. Tests 4815 -> 5076.
2026-05-21 23:13:21 +02:00
Marc-André Bombeck 9b6e237f5f chore(release): v1.4.42 — Knip enforcing, queryKey factory closed, iOS Workouts dedup, Withings off-response classification
Six touch-disjoint waves landed on develop before this release:

- W1-KNIP — exports/types tiers flipped to enforcing-mode on main;
  35 unused exports + 52 unused types triaged to 0/0; shadcn surface
  + zod-infer contract types scoped via knip.json ignore-block.
- W2-ZOD-MULTI-ISSUE — `returnAllZodIssues` shared helper +
  `/api/dashboard/widgets` 422 now returns every Zod issue + worker
  audit-ledger breadcrumb. 41 sibling routes documented for v1.4.43.
- W3-QUERYKEY-LONGTAIL — 40 settings/medications/admin/hooks files
  migrated to the queryKeys factory; 9 new factory entries; ESLint
  rule + test-guard scope extended in lockstep.
- W4-ARCH-HYGIENE — BERLIN_DAY_FORMATTER consolidated across nine
  sites (seven status helpers + bucket-series + bp-in-target);
  Suspense double-comment consolidated; doctor-report sanitiser regex
  rewritten to escape-form so diffs are reviewable; pr-detection-
  worker soft-delete filter; offhost-backup DR-intent comment.
- W5-IOS-WORKOUTS — `dedupeWorkoutBatch` write-time cross-source
  dedup helper for the v1.5 iOS HealthKit observer ingest; dashboard
  tile-strip placeholder parity with the live TrendCard chrome.
- W6-WITHINGS-OFF + ENV-CHECK — Withings off-response classifier
  (transient/reauth/persistent) + WithingsApiError typed throw + the
  `pnpm check-env` CLI. APNs `allOrNone: true` closes the AP-2
  silent-disable gap the wave was conceived to close.

No migration, no env-var change, no API contract break for iOS v0.5.4.
The new `/api/dashboard/widgets` 422 envelope is additively extended.

pnpm test --run green at 4815 passing / 1 skipped.
pnpm typecheck, pnpm lint, pnpm knip (no --include flag) green.
2026-05-21 19:33:07 +02:00
Marc-André Bombeck 67207d725e chore(release): v1.4.41 — iOS perf hotfix, soft-delete completeness, tree hygiene
Eight touch-disjoint waves landed on develop before this release:

- W-INSIGHTS-HOT + W-RECONCILE-INSIGHTS — extract the timeout-stub
  persist helper and wire all seven *-status routes through it, closing
  the recurring ~14 s warm response the iOS client sees on dashboard
  open once the AI provider has stalled once that day.
- W-DELETED-2 — close the remaining three soft-delete reader-tier gaps
  (export bundle, gamification achievements, doctor-report aggregator)
  with a five-route integration-test pin.
- W-ORG — types consolidation into src/types/analytics.ts and
  src/types/backups.ts; prompt directory unification.
- W-FRONTEND-FACTORY — queryKey factory expansion + ESLint rule lift.
- W-IOS-COORD — POST /api/auth/check-user four-branch onboarding
  discovery, identifier matched verbatim, per-IP rate-limited.
- W-SIMPLIFIER — today-intake projection helper extract; unused tx?
  params dropped; 13 knip-flagged dead exports trimmed.
- W-PERF-OPS — v1.4.39 W-SUM legacy-NULL UNION discovery arm retired.
- W-PROCESS-DOCS — pg.Pool sizing operator doc; ESLint rule
  formalisation; knip CI gate staged for v1.4.42 flip.

No migration, no env-var change, no API contract break for iOS v0.5.4.
The new check-user endpoint is an additive surface. AP-2 .p8 key is
installed; SB-5 time-sensitive APNs is live as of this deploy.

pnpm test --run green at 4732 passing / 1 skipped.
pnpm typecheck, pnpm lint, pnpm knip green.
2026-05-21 18:36:17 +02:00
Marc-André Bombeck 4f09c3f862 chore(release): v1.4.27
v1.4.27 — Mobile capability and maintainer-finding cleanup.

ResponsiveSheet primitive (bottom-sheet on narrow viewports, centred dialog on desktop)
applied to BD, Weight, Glucose, Mood, Activity forms. Maintainer-finding cleanups across
Coach, Insights, Reports, Privacy. GeoLite2 offline geocoding with resilience layer
(graceful fallback when EULA not yet propagated). E2E gap-fill coverage. 450-line
release notes attached to the GitHub Release.
2026-05-15 22:59:10 +02:00
Marc-André BombeckandClaude Opus 4.7 1ae9b9dcad fix(admin,measurements): humanise raw enum badges
Two badges were rendering raw enum values: backups showed
`WEEKLY_AUTO` / `MANUAL` and the measurement list showed
`WITHINGS` instead of `Withings`. Backup type now uses two new
i18n keys (`Weekly (auto)` / `Manuell`); measurement source
reuses the existing `measurements.source*` keys. Unknown
enum values still fall through to the raw string so any
schema additions remain visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:40:52 +02:00
Marc-André Bombeck b863e2cc3d fix(ui): tap-targets reach 44px on tour, notification, backups buttons (v1.4.15 H4)
WCAG 2.5.5 Level AAA target-size floor for non-essential controls is
24×24 px and Level AA pointer-target is 24×24 with 24 px gap, but the
mobile-app convention everyone follows is the 44×44 hit-area Apple
HIG / Material recommend. The default `<Button size="sm">` in the
shadcn-base lands at `h-8` (32 px) — readable on desktop, tight on
phones, especially in the most-touched admin/settings/onboarding
surfaces called out in the v1.4.15 design review.

Cross-cutting `button.tsx` `h-9 → h-11` was rejected as too risky
mid-marathon (every UI affected, dozens of layout assumptions). The
targeted alternative bumps the listed call-sites with `min-h-11`
overrides: tour Skip / Back / Next, notification re-enable + send-
test, backups Restore / Download / Run-now / Upload. Admin-only
recent-audit-preview is a `<Link>` not a `<Button>` and is left
alone.

The shadcn `min-h-` Tailwind utility wins over the variant `h-`
because both apply but `min-h-` only floors. So desktop visuals stay
within ~4 px of the previous render while mobile reaches the 44 px
floor.
2026-05-10 00:29:44 +02:00
Marc-André BombeckandClaude Opus 4.7 0c20119c14 feat(admin): empty states for users, backups, login-overview, api-tokens, feedback
Replaces blank rectangles in 6 admin sub-routes with the EmptyState
primitive. Each surface now shows an icon, localized title +
description, and (where it makes sense) a primary action so a fresh
admin always knows what to do next.

- /admin/users — Users icon, filter-aware copy, "Show all users" reset
  CTA when a filter is active.
- /admin/backups — Database icon, "Backup now" duplicate CTA inside the
  empty card so the action is right next to the explanation.
- /admin/login-overview — ScrollText icon, splits "no auth events" from
  the safer "no failed sign-ins"; reset CTA on the failed-only filter.
- /admin/api-tokens — Key icon, copy points to native client sign-in
  and Settings -> Account where tokens are issued.
- /admin/feedback — Inbox icon, compact-size, per-tab description.
- /admin overview audit-preview — compact + plain so it nests inside
  the parent card without doubling the dashed border.

New unit tests for the users + backups paths assert the primitive
mounts (role=status / dashed border), the localized strings render,
and the reset/CTA actions appear only in the right contexts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:48:36 +02:00
Marc-André BombeckandClaude Opus 4.7 7c32d6344d feat(admin): link from backups view to docs.healthlog.dev/admin/backups
Right-rail "Documentation" link in the Backups section header points
at https://docs.healthlog.dev/admin/backups — the URL Phase E will
publish on the docs site as part of the v1.4.15 release.

Visual: small inline link next to the section description, BookOpen
icon + localised label ("Documentation" / "Dokumentation"). Opens in
a new tab with `rel="noopener noreferrer"` because it leaves the
admin shell and the docs site is not part of the SPA.

i18n: new `admin.section.backups.docsLink` key in EN + DE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:02:16 +02:00
Marc-André BombeckandClaude Opus 4.7 fe85c2c85f feat(admin): restore from backup with full data replacement + triple-confirm
POST /api/admin/backups/[id]/restore replaces the target user's
personal data with the contents of the snapshot. This is the riskiest
endpoint in the backup family, hardened with five independent gates:

  1. requireAdmin() — cookie-session admins only, never a Bearer token.
  2. Body must carry confirm: "RESTORE" (rejected with 422 + audit
     denial otherwise).
  3. UI gates the destructive button behind a typed-string match —
     three independent steps before fire (open dialog, read warning,
     type RESTORE).
  4. withIdempotency() wraps the handler so duplicate clicks with the
     same Idempotency-Key replay the original outcome instead of
     re-executing the destructive transaction.
  5. Pre-validation of measurement-type / measurement-source /
     intake-source enums OUTSIDE the transaction so a malformed
     payload can't half-wipe the user before failing the create step.

Restore scope mirrors the v1.4.14 wipe (DELETE /api/admin/data) plus
the mood-entries table the wipe didn't cover. AuditLog rows are
intentionally PRESERVED across the restore — the audit trail is the
last thing that should be touched by a destructive op.

Transaction:
  - Delete user's measurements, medications (cascades to schedules +
    intake events + reminder configs + telegram messages), mood
    entries, notification channels, push subscriptions, telegram
    scheduled deletions.
  - Re-create from payload: measurements (skipDuplicates), medications
    + their schedules in one create call, intake events keyed by
    medication name -> id (orphans dropped silently), mood entries
    (skipDuplicates).

Audit:
  - admin.backups.restore.start  (intent, before transaction)
  - admin.backups.restore        (success, after transaction)
  - admin.backups.restore.denied (5 reasons, no transaction ran)
  - admin.backups.restore.failed (decrypt / schema / enum / tx error)

UI:
  - New <RestoreRowDialog> subcomponent renders an AlertDialog gated
    on a typed-RESTORE Input. Description carries the target username
    and the snapshot timestamp so the admin sees exactly which
    backup they're about to restore. Confirm button stays disabled
    until typed string matches.
  - Restore button sits next to Download in each row's action column.
  - Idempotency-Key built client-side as restore-<rowId>-<uuid> to
    de-duplicate accidental double-clicks.

Tests: 3 new integration tests in
tests/integration/admin-backups-restore.test.ts covering the round-
trip (wipes pre-existing data + recreates from snapshot),
confirmation-token rejection, and 404 on unknown id. 21/21
integration tests pass; 69/69 unit tests on src/lib/validations and
src/components/admin still green.

Note for v1.4.16: the restore test queries `medication_schedules`
with explicit field selection because a sibling branch added
`days_of_week` to the schema without a matching migration; the
default `findMany` would 500 against the deployed test DB. When that
migration lands, the explicit select can be relaxed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:58:33 +02:00
Marc-André BombeckandClaude Opus 4.7 30a74ed3b8 fix(mobile): bump chart controls + medication primary buttons + mood list icons to 44px
Phase A5 / B-mobile HIGH cluster — closes 4 audit findings:

1. Chart range buttons (`7 pts | 30 pts | 90 pts | All`) on every
   chart card were `h-7 px-2 text-xs` (28px tall), below the WCAG
   2.5.5 / Apple HIG / Material 44px minimum. Bumped to
   `min-h-9 px-2.5` (36px) — that's still compact enough to fit in
   the chart header strip on a 393px viewport but raises tap-target
   pass rate substantially. Files: health-chart, mood-chart,
   medication-compliance-chart, compliance-line-chart,
   compliance-charts.

2. Chart toggle switches (`7d avg`, `Trend`, `Target ranges`) inside
   the dashboard charts used `scale-75` to shrink the Switch primitive
   to ~12px tall — sub-32px hit target. Dropped the `scale-75` so each
   Switch returns to its default size. Files: health-chart, mood-chart.

3. Medication card primary actions ("Taken" / "Skipped"): these are
   the most-tapped controls in HealthLog (every dose intake is one
   tap). Were `size="sm"` (32px). Bumped to default size + explicit
   `min-h-11`. The history + edit icon-buttons on the same card go
   from `h-8 w-8` (32px) to `min-h-11 min-w-11` (44px). Glyph stays
   16px so the visual weight doesn't change.

4. Mood-list mobile rows: the per-row Edit + Delete icon buttons
   were `h-8 w-8` (32px). Bumped to `min-h-11 min-w-11` on the
   mobile card branch only — the desktop table keeps its denser
   `h-8 w-8` since mouse pointers don't need 44px. `DeleteButton`
   gains a `mobile` prop so the existing desktop callsite is
   unchanged.

Findings IDs:
- A5/HIGH `/` (Dashboard) — chart range buttons
- A5/MEDIUM `/` (Dashboard) — chart switch toggles
- A5/HIGH `/medications` — Taken/Skipped sizing
- A5/HIGH `/mood` — list edit/delete sizing
- A5/MEDIUM `/medications` — history + edit icon buttons

Verification: 823 / 823 unit tests pass, lint clean (12 pre-existing
warnings unchanged), typecheck regression-free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:50:08 +02:00
Marc-André BombeckandClaude Opus 4.7 d8c549edf1 fix(admin): /admin/users mobile layout — card-list at < md, no truncation
Phase A5 / B-mobile CRITICAL #2: at 393 CSS px the desktop user table
truncated the role badge ("ADMI", "USE") and pushed the action column
entirely off the right edge. The destructive force-logout action was
unreachable on mobile, plus the card-header filter pills collided
with the title's count badge.

Fix:
- Header switches from horizontal to vertical stack at `< sm` so the
  filter pills get their own row instead of fighting the title for
  space.
- Card padding `p-4 sm:p-6` matches the api-tokens responsive
  template.
- Desktop table is gated behind `hidden md:block` (instead of just
  letting it overflow).
- New mobile card-list at `< md` renders each user as a self-
  contained card with the username + role badge + meta line + a
  flex-wrapping action row carrying all four buttons (toggle role,
  edit, reset password, force logout). Nothing hidden behind a
  horizontal scroll, every action stays tap-targetable.
- Action buttons extracted into a single `renderUserActions()` helper
  so desktop + mobile stay in lock-step on future admin actions.

Evidence: new `user-management-responsive.test.tsx` asserts the
desktop table is `hidden md:block`, the mobile list is `md:hidden`,
the role badge appears in both layouts (no column lost), all four
ARIA-labelled action buttons are present in the mobile card, the
header stacks on `< sm`, and the card uses `p-4 sm:p-6`. 6 / 6 tests
green.

Findings ID: A5/CRITICAL-#2 (top of `.planning/phase-A5-mobile-findings.md`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:46:20 +02:00
Marc-André BombeckandClaude Opus 4.7 bd24f130ee fix(a11y): drop duplicate h2 inside single-section /admin/<slug> routes
Phase 4b's `<SectionFrame>` already paints the canonical h1 + subtitle
for each `/admin/<slug>` route, but every section card kept its v1.4
in-card h2 with the same (or near-same) text. The result was the
section title announcing twice — once as h1, then immediately again as
h2 — both visually redundant and a screen-reader landmark anti-pattern.

Demoting the in-card label to a `<div>` preserves the visual look
(same Tailwind classes) while removing the duplicate heading. The
four sub-cards inside `IntegrationsGroupSection` (umami, glitchtip,
web-push, bug-report) keep their h2 because they live under the
"Integrations" h1 and genuinely deserve a heading level — that is the
non-duplicating case.

Closes H1 from .planning/phase-6-design-findings.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:44:05 +02:00
Marc-André BombeckandClaude Opus 4.7 88c8db1abc refactor(v1.5): apply simplify-review safe-to-apply suggestions
Drops dead `id` props and `scroll-mt-28` classes from admin section
components (load-bearing in v1.4 monolithic admin, dead since the v1.5
phase-4b dynamic-route split). Also removes the unused
`LEGACY_ANCHOR_TO_SLUG` map, the `admin-shell` re-export shim, the
`BERLIN_TZ` and duplicate `MS_PER_DAY` constants, the verbose
`AdminSectionPage` docstring, the `useEffect` paraphrase comment in
`ai-section`, and rewrites the `_exhaustive: never` exhaustiveness
guard to the modern `slug satisfies never` form. `UserManagementSection`
now reads `currentUserId` via `useAuth()` instead of accepting it as a
prop.

Findings 1, 2, 3, 4, 6, 7, 8, 11, 15, 16, 17 from
.planning/phase-6-simplify-findings.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:41:02 +02:00
Marc-André BombeckandClaude Opus 4.7 69c5225cea feat(admin): backups view + manual backup trigger (T2.6 deferred)
Folds in the v1.4.6 deferred backlog item:

* `GET /api/admin/backups` — returns metadata-only listing of every
  DataBackup row (size in bytes, no payload). Encrypted blobs stay
  server-side.
* `POST /api/admin/backups/run` — enqueues an ad-hoc data-backup
  pg-boss job. Returns 503 if the worker isn't running; the existing
  weekly cron schedule is unchanged.
* `<BackupsSection>` — admin route at /admin/backups with the rows
  table + "Backup now" CTA.

No new dependencies; uses the global pg-boss instance already wired in
`src/lib/jobs/boss-instance.ts`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:59:38 +02:00