Commit Graph
9 Commits
Author SHA1 Message Date
Marc-André Bombeck 45ca6ff24b fix(admin): name the configured geo provider in the system snapshot
The admin system snapshot hardcoded "ipwho.is" for the offline-geo-off
state, so an operator who points IP_GEO_LOOKUP_URL at another provider
still read the wrong host. Resolve the actual provider host from the env
and surface it through /api/version, so the snapshot reads
"Online (ip-api.com)" — the host it really queries.
2026-06-30 18:08:57 +02:00
Marc-André Bombeck 1f4ba5939d style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
2026-06-19 22:20:31 +02:00
Marc-André Bombeck eb1bbef295 chore(license): move to PolyForm Noncommercial 1.0.0
The project licence changes from AGPL-3.0 to the PolyForm Noncommercial
License 1.0.0 for this and future versions: free to use, modify and
self-host for noncommercial purposes; commercial use needs a separate
arrangement. Releases up to and including v1.15.18 were published under
AGPL-3.0 and remain available under that licence. LICENSE carries the
verbatim PolyForm text, and the SPDX identifier is updated in
package.json, the version endpoint, the OpenAPI info block, the image
labels and the README.
2026-06-10 11:18:12 +02:00
Marc-André BombeckandClaude Opus 4.7 425503e095 feat(security): safeFetch wrapper with manual redirect and timeout defaults
Closes issue 218. Flips the convention from "remember to opt in to
redirect: manual" to "remember to opt out". Every outbound fetch that
doesn't pin a hard-coded host now routes through safeFetch and inherits
AbortSignal.timeout(15_000) so a tar-pit upstream can no longer pin a
worker indefinitely. The local AI client also gates on
requirePublicHost: true since its host is user-supplied; operators
running self-hosted Ollama / LM Studio on an RFC1918 address keep their
escape hatch via ALLOW_LOCAL_AI_PRIVATE_HOSTS=true (also enforced at
write-time in /api/user/ai-provider).

14 outbound call sites migrated. The four that already pinned manual
redirect (the historic templates) keep their semantics; the wrapper
preserves an explicit init.redirect. Adds undici as an explicit
dependency so the connect-time pin landing alongside this can rely on
the documented API rather than a transitive hop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:32:11 +02:00
Marc-André Bombeck 324d4bb411 chore(release): v1.4.44 — REG-11 dashboard summary fix (iOS Home tile sparkline + value)
iOS-operator-blocking hotfix for REG-11 — Home dashboard tile renders
neither chart nor latest value for BP / Puls / Körperfett when the
most recent reading is older than 7 days. Root cause was in
`/api/dashboard/summary` SQL gates; `latestIn7d` + `sparkBuckets` both
required `measured_at >= sevenDaysAgo`, which returned empty for
sparse accounts, leaving the iOS tile with `latestValue: null` +
`sparkline: []` to render from. Detail-screen
(`/api/measurements/series?kind=…`) escaped because of its broader
window.

Server-side changes:
- `latestIn7d` → `latestEver` (drops 7-day filter; DISTINCT ON returns
  latest reading EVER per type, bounded at N_metrics rows).
- `sparkBuckets` rewritten via ROW_NUMBER() OVER (PARTITION BY type
  ORDER BY bucket_start DESC) so the trailing N daily buckets per
  type render regardless of age, bounded at SPARK_DAYS × N_metrics.
- BP + Pulse metrics.push gated on `latest || allTimeCount > 0` like
  BodyFat already is — accounts with zero readings ever no longer get
  empty placeholder tiles.
- 4 new regression tests in summary/__tests__/route.test.ts cover
  sparse accounts, zero-data accounts, within-7d guards.

Other develop work bundled in this release:
- W4-QoL — copy nits.
- W5-WORKOUTS — user source priority threaded through write-time
  picker + batch route.
- W7-WITHINGS-RELIABILITY — response classifier + sync hardening.
- W8-OPS — ops phase docs.
- B11 — NEXT_PUBLIC_APP_VERSION build-arg routing so /api/version
  cannot drift from CI tag.
- env-check workflow — manifest / example lockstep enforcement.

Resolves PR #194 (squash-merge produced via local -X theirs strategy
because GitHub's UI couldn't auto-resolve develop's post-v1.4.42
contiguous work against the v1.4.42 squash on main; resulting tree is
byte-identical to develop's HEAD).
2026-05-21 22:07:32 +02:00
Marc-André Bombeck 232ea43d99 chore(release): v1.4.28.1
## [1.4.28.1] — 2026-05-16 — Dashboard-save hotfix

"Speichern" on the Dashboard works again for every account whose
layout was last persisted before v1.4.28. The v1.4.28 retire of the
`glp1` widget id left an orphan entry in `dashboardWidgetsJson` for
legacy users, which made the save validator reject the payload; the
resolver now filters unknown widget ids on read.

### Fixed

- **Dashboard save against legacy layouts.**
  `resolveDashboardLayout` in `src/lib/dashboard-layout.ts` drops
  widget ids that are no longer in the registry before returning
  the layout, so the `dashboardWidgetsJson` round-trip
  (`GET /api/me/dashboard` → user edit → `PUT /api/me/dashboard`)
  succeeds even when the persisted JSON still names `glp1` or any
  other retired widget.

### Tests

- `src/lib/__tests__/dashboard-layout.test.ts` gains a case for the
  retired-id filter (19/19 green).
2026-05-16 09:28:40 +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 622099c0fd fix(settings): About + API surfaces — restructure, fix update check, fit tables
Bucket B of the v1.4.3 polish pass.

Three issues, one commit because they're the same surface:

1. **API & Tokens tables overflowed the settings content column.**
   Each table set `min-w-[760px]` (or 860px) so the table never
   compressed below that width. On mobile this is intentional (it
   gives the inner cells breathing room while the outer wrapper
   horizontally scrolls). On md+ the parent column is smaller than
   the min-width on common laptop sizings (~800px content area when
   the browser isn't full-width), which forced an inline scrollbar
   for no benefit. Switched to `min-w-[760px] md:min-w-0` per table
   so the constraint releases at the breakpoint where the layout
   stops scrolling.

2. **"Auf Updates prüfen" button silently did nothing.** The
   client-side handler called `https://api.github.com/.../releases/
   latest` directly. Production CSP locks `connect-src` down to
   `'self'` + `https://wbsapi.withings.net`, so the fetch was
   blocked at the browser before it left the tab. Replaced with a
   server-side proxy at `/api/version/check-updates` that fetches
   GitHub for the user, compares the running `package.json` version
   against `tag_name`, and returns one of `up_to_date | newer_
   available | unknown`. The route is auth-gated (cookie OR bearer)
   so anonymous traffic can't burn through GitHub's 60-req/hr anon
   rate limit on the deployment's IP. The route also annotates the
   external call into the Wide Event so a future spike is visible
   in the logs.

3. **The About surface had no headings and a stray Badge box.** The
   v1.4.2 version put the License inside a `<Badge variant=
   "outline">`, which created a small framed pill that read as a
   separate widget rather than a peer of the version number. Below
   that, the Sources/Changelog/Docs and the update-check button
   each lived in their own card with no h2 — three nameless cards
   stacked on the page. Restructured into three titled cards:

     - **HealthLog** — identity, with version + license rendered
       inline as plain monospace pairs (no Badge, no nested grid).
       Build SHA + build timestamp ride along on the same row when
       set.
     - **Quellen & Dokumentation / Sources & docs** — links unchanged
       but now under their own h2.
     - **Updates** — manual `Check now` button + a passive "Last
       checked: …" line that pulls from localStorage so the user
       can see when the previous check ran. On mount, if the last
       successful check is >24h ago (or has never run), the page
       auto-checks once. The 24h window is a deliberate compromise
       between "always fresh" and "GitHub anon rate limit". The
       error path renders a non-blocking "Couldn't reach GitHub"
       message with the alert role so screen readers announce it.

i18n keys touched in both `de.json` + `en.json`:
  - `settings.about.linksHeading` (new)
  - `settings.about.updatesHeading` (new)
  - `settings.about.checkUpdates` (label shortened)
  - `settings.about.checking` (button busy state — new)
  - `settings.about.lastChecked` / `lastCheckedNever` (new)
  - `settings.about.checkFailed` (new — replaces the misleading
    `testConnection.errors.connection_failed` reuse)

Tests updated for the new heading + button strings; 669/669 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:29:03 +02:00
Marc-André BombeckandMarc-André Bombeck f131d6be2c feat(api): public /api/version endpoint for the v1.4 About surface (#128)
Adds a public GET endpoint that exposes the running build's version,
optional Git SHA, optional ISO build timestamp, license, and
canonical links (repository, changelog, docs site). Three consumers
already lined up against this:

- The Settings → About section (v1.4 plan A3) renders version + build
  metadata + a "Check for updates" button.
- The footer of every page can show the version dezent without
  another roundtrip.
- The "Check for updates" button compares the returned version
  against the GHCR API.

Build-time env vars (`NEXT_PUBLIC_APP_BUILD_SHA`,
`NEXT_PUBLIC_APP_BUILT_AT`) are read straight off `process.env` —
the existing `docker-publish.yml` workflow can wire them once it
needs to. Local `pnpm dev` returns `null` for both fields and the UI
falls back to "development" wording.

Static export (`force-static`) so the endpoint adds zero load on the
DB and survives in the static-cache page chunks.

Tests: 395 → 400 (+3 for the version envelope shape and env-var
plumbing). The remaining About UI lands in a follow-up so this PR
stays scoped to the API contract.

Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
2026-05-08 12:17:40 +02:00