mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
chore(release): v1.4.25
Largest feature delta in the v1.4.x line. Insights expands into seven dedicated metric sub-pages; GLP-1 medication tracking lands end-to-end (drug-level chart with Research Mode acknowledgment, EMA titration ladder, cadence + compliance, side-effect taxonomy, pen/vial inventory); cross-source priority becomes a two-axis resolver; per-user timezone threads through ten analytics surfaces; Withings activity + sleep v2 sync; onboarding rebuilt as a nested-route wizard; Personal Records ship end-to-end; Health Score provenance accordion; Coach runs native first-party prompts across six locales with a 1800-assertion refusal-probe matrix; OpenAPI drift gate hard-fails; multi-arch GHCR image. Migrations 0043 through 0060 are additive and forward-only. PROMPT_VERSION 4.24.0 → 4.25.0 with GROUND RULE 9 (refuses dose recommendations) and GROUND RULE 15 (refuses drug-level estimates with MDR + MDCG 2021-24 cites). See CHANGELOG.md for the full per-section delta.
This commit is contained in:
1 parent
de0689e419
commit
17b8d8d2c2
528 files changed
+104033
-6524
No files matched your search
@@ -0,0 +1,81 @@
|
||||
name: Translation feedback
|
||||
description: Report a wording, grammar, or register problem in HealthLog's AI-initial locales (FR / ES / IT / PL).
|
||||
title: "[Translation] "
|
||||
labels: ["i18n", "translation-feedback"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
HealthLog ships German and English as maintainer-curated translations. French, Spanish, Italian, and Polish ship as **AI-initial translations** — the prose passes the locale-integrity test (full key parity, no empty values) but has not been hand-reviewed for register or regional vocabulary.
|
||||
|
||||
This template captures concrete fixes you would like to see. Phrasing nuance, regional vocabulary (e.g. "Tú" vs "Usted"), gendered forms, and domain-specific health terms are all in scope. The maintainer applies fixes during the regular release cycle.
|
||||
|
||||
For wholly new locale requests (e.g. "please add Portuguese"), open a [Discussion](https://github.com/MBombeck/HealthLog/discussions) instead.
|
||||
- type: dropdown
|
||||
id: locale
|
||||
attributes:
|
||||
label: Locale
|
||||
options:
|
||||
- French (fr)
|
||||
- Spanish (es)
|
||||
- Italian (it)
|
||||
- Polish (pl)
|
||||
- Other (specify under "Surface")
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: surface
|
||||
attributes:
|
||||
label: Where you saw it
|
||||
description: |
|
||||
Page / component / dialog name. Include the route (e.g. `/insights`, `/settings/notifications`) and, if possible, the i18n key from the rendered DOM (open DevTools, look for the visible string).
|
||||
placeholder: |
|
||||
Route: /insights/blood-pressure
|
||||
Component: blood-pressure trend card header
|
||||
Key (if known): insights.bp.trendCard.title
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current wording
|
||||
description: Paste the exact phrase as it appears in the app today.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: suggested
|
||||
attributes:
|
||||
label: Suggested wording
|
||||
description: |
|
||||
How should it read instead? Include reasoning if the change is more than a typo — register (formal vs. informal), regional vocabulary, medical terminology, or grammar.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: kind
|
||||
attributes:
|
||||
label: Kind of issue
|
||||
options:
|
||||
- Grammar / spelling
|
||||
- Wrong word (translation error)
|
||||
- Awkward / unnatural phrasing
|
||||
- Wrong register (too formal / too casual)
|
||||
- Medical terminology
|
||||
- Regional vocabulary
|
||||
- Untranslated string (English fall-through)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Screenshots, links to related issues, related strings on the same page, etc.
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Pre-flight checks
|
||||
options:
|
||||
- label: I confirmed this is not a duplicate of an existing translation issue.
|
||||
required: true
|
||||
- label: My suggestion respects the maintainer's editorial voice (professional, neutral, plain prose — no marketing-speak).
|
||||
required: true
|
||||
@@ -20,16 +20,43 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build & push image
|
||||
runs-on: ubuntu-latest
|
||||
# 25 min wall clock — a healthy amd64-only build finishes inside 12 min.
|
||||
# The qemu-arm64 path that was hanging at 47 min on `[linux/amd64 runner
|
||||
# 2/14]` is gone; see commit message for v1.4.16 C5 rationale below.
|
||||
# ── Stage 1 ────────────────────────────────────────────────────────
|
||||
# Build each architecture on its own native GitHub-hosted runner so
|
||||
# Next.js's static-page workers run on real silicon instead of QEMU
|
||||
# user-mode emulation. The v1.4.16 C5 attempt at
|
||||
# `platforms: linux/amd64,linux/arm64` on a single amd64 runner
|
||||
# deterministically SIGILL-crashed Next 15's static export under
|
||||
# qemu-arm64 (`Next.js build worker exited with code: null and signal:
|
||||
# SIGILL`); v8's optimising tier emits instructions qemu cannot
|
||||
# reliably translate. ubuntu-24.04-arm is a standard GitHub-hosted
|
||||
# arm64 runner, free for public repositories, and native — no qemu in
|
||||
# the loop.
|
||||
#
|
||||
# Each platform pushes its layer set by digest only (`outputs: push-by-digest=true`).
|
||||
# The `merge` job below stitches the two digests into a multi-arch
|
||||
# manifest in a third step, so consumers see a single tag that
|
||||
# auto-selects on `docker pull` per local platform.
|
||||
build:
|
||||
name: Build ${{ matrix.platform }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
# 25 min wall clock — native amd64 build finished inside 12 min
|
||||
# at v1.4.24; the native arm64 path is comparable. The qemu-arm64
|
||||
# hang at 47 min on `[linux/amd64 runner 2/14]` is what motivated
|
||||
# this matrix in the first place.
|
||||
timeout-minutes: 25
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
cache_scope: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
cache_scope: arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -38,6 +65,18 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# GHCR rejects repository refs with uppercase letters. `IMAGE_NAME`
|
||||
# is derived from `github.repository`, which preserves the case of
|
||||
# the GitHub org/user (`MBombeck/HealthLog`). `docker/metadata-action`
|
||||
# lowercases its `images:` input transparently, so the legacy
|
||||
# single-job workflow worked; the multi-arch matrix now feeds
|
||||
# `outputs:` of build-push-action directly and needs the lowercase
|
||||
# form there too. Resolve it once at the top of the job and reuse
|
||||
# via $GITHUB_ENV.
|
||||
- name: Lowercase image ref
|
||||
run: |
|
||||
echo "IMAGE_REF=${REGISTRY}/${IMAGE_NAME,,}" >> "$GITHUB_ENV"
|
||||
|
||||
# Skip login on PRs from forks — they don't have package:write.
|
||||
# Image is built but not pushed in that case.
|
||||
- name: Log in to GitHub Container Registry
|
||||
@@ -48,11 +87,105 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Compute image labels
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_REF }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=HealthLog
|
||||
org.opencontainers.image.description=Self-hosted health-tracking PWA
|
||||
org.opencontainers.image.licenses=AGPL-3.0-only
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
|
||||
# Per-ref + per-platform gha cache scope. Tag builds warm-start
|
||||
# off main's last good layer set for the same platform; tag and
|
||||
# main builds at the same SHA do not contend on writes, and
|
||||
# amd64 / arm64 caches stay isolated from each other (a stale
|
||||
# arm64 layer never poisons an amd64 build).
|
||||
- name: Build & push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: |
|
||||
type=image,name=${{ env.IMAGE_REF }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
|
||||
cache-from: |
|
||||
type=gha,scope=build-${{ matrix.cache_scope }}-${{ github.ref_name }}
|
||||
type=gha,scope=build-${{ matrix.cache_scope }}-main
|
||||
cache-to: type=gha,mode=max,scope=build-${{ matrix.cache_scope }}-${{ github.ref_name }}
|
||||
provenance: true
|
||||
sbom: true
|
||||
|
||||
# Persist the per-platform digest so the `merge` job can stitch
|
||||
# both into a single multi-arch manifest. One artifact per
|
||||
# platform avoids upload races.
|
||||
- name: Export digest
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest='${{ steps.build.outputs.digest }}'
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest artifact
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digest-${{ matrix.cache_scope }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# ── Stage 2 ────────────────────────────────────────────────────────
|
||||
# Stitch the per-platform digests into a single multi-arch manifest
|
||||
# under all the tags `docker/metadata-action` would normally apply.
|
||||
# `docker buildx imagetools create` is a manifest-only operation —
|
||||
# no layers are re-uploaded, the existing per-platform digests are
|
||||
# referenced in place. This is the same end state as the
|
||||
# single-buildx `platforms:` path would produce, just assembled in
|
||||
# the registry instead of on one cross-emulated runner.
|
||||
merge:
|
||||
name: Merge multi-arch manifest
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
needs: [build]
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Download digest artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# GHCR requires the repository ref to be lowercase. See the same
|
||||
# step in the `build` job above for the longer rationale.
|
||||
- name: Lowercase image ref
|
||||
run: |
|
||||
echo "IMAGE_REF=${REGISTRY}/${IMAGE_NAME,,}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Compute image tags & labels
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
images: ${{ env.IMAGE_REF }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
@@ -66,44 +199,16 @@ jobs:
|
||||
org.opencontainers.image.licenses=AGPL-3.0-only
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
|
||||
# v1.4.16 C5 — drop linux/arm64 from the multi-arch build.
|
||||
#
|
||||
# Root cause of the main-branch hang since v1.4.14: the qemu-arm64
|
||||
# path (`docker/setup-qemu-action` + `tonistiigi/binfmt`) was
|
||||
# SIGILL-crashing Next.js's static-page-generation workers
|
||||
# (`Next.js build worker exited with code: null and signal: SIGILL`
|
||||
# at 64-66/86 pages). V8's optimising tier emits CPU instructions
|
||||
# that qemu's user-mode emulation cannot reliably translate under
|
||||
# heavy load, and Node 22 + Alpine 3.21 hit it deterministically.
|
||||
# Tag-builds appeared to "work" only because they happened to
|
||||
# finish their static pages before the crash window; main-branch
|
||||
# builds rarely got that lucky.
|
||||
#
|
||||
# Marc's prod target is x86_64 only (Coolify on apps-01, all other
|
||||
# self-hosted instances we know of); the arm64 image was a
|
||||
# courtesy build for OSS users, not production. v1.5 will re-add
|
||||
# arm64 via a matrix that uses the GitHub-hosted `ubuntu-24.04-arm`
|
||||
# runner (free for public repos, native arm64, no qemu) — see
|
||||
# `docs/audit/v1416-auto-deploy-fix.md` for the broader CI plan.
|
||||
#
|
||||
# Per-ref gha cache scope (kept from v1.4.15 C3): tag + main
|
||||
# builds at the same SHA do not contend on writes; tag warm-starts
|
||||
# off main's last good build for layer reuse.
|
||||
- name: Build & push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: |
|
||||
type=gha,scope=build-${{ github.ref_name }}
|
||||
type=gha,scope=build-main
|
||||
cache-to: type=gha,mode=max,scope=build-${{ github.ref_name }}
|
||||
provenance: true
|
||||
sbom: true
|
||||
- name: Create multi-arch manifest
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.IMAGE_REF }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect published manifest
|
||||
run: |
|
||||
docker buildx imagetools inspect '${{ env.IMAGE_REF }}:${{ steps.meta.outputs.version }}'
|
||||
|
||||
# Phase C2 (v1.4.15): trigger Coolify deploy automatically once GHCR has
|
||||
# the new image. Removes the manual SSH + force-pull recipe documented
|
||||
@@ -115,21 +220,47 @@ jobs:
|
||||
# outage must not retroactively fail the build. Marc still has the
|
||||
# manual recipe as fallback.
|
||||
#
|
||||
# Required GitHub repository secrets (set by Marc once):
|
||||
# Required GitHub repository secrets (set by the maintainer once):
|
||||
# COOLIFY_WEBHOOK = full deploy URL incl. ?uuid=...&force=false
|
||||
# COOLIFY_TOKEN = Bearer token from Coolify UI -> Keys & Tokens
|
||||
# Both secrets are referenced exclusively here. If either is unset, the
|
||||
# step short-circuits to a skip rather than calling Coolify with empty
|
||||
# auth.
|
||||
#
|
||||
# v1.4.26 P6-3 — maintainer toggle.
|
||||
# Three releases in a row shipped via the host-side SSH-retag fallback
|
||||
# because the secrets above silently absent + the warn-only skip
|
||||
# below let the workflow stay green. The `vars.COOLIFY_AUTO_DEPLOY`
|
||||
# repository variable now distinguishes the two intended modes:
|
||||
#
|
||||
# COOLIFY_AUTO_DEPLOY=on → if secrets present, deploy; if absent,
|
||||
# FAIL the step so the maintainer sees the
|
||||
# missing-secret gap on the next release.
|
||||
# COOLIFY_AUTO_DEPLOY=off → unconditional skip with a notice. Use
|
||||
# this when the host-side SSH recipe is
|
||||
# the intended deploy path.
|
||||
# (unset / any other) → warn-and-skip (legacy behaviour).
|
||||
#
|
||||
# Set the variable under repo Settings → Secrets and variables →
|
||||
# Actions → Variables (NOT Secrets — it is intentionally readable).
|
||||
- name: Trigger Coolify deploy
|
||||
if: github.event_name != 'pull_request'
|
||||
continue-on-error: true
|
||||
env:
|
||||
COOLIFY_WEBHOOK: ${{ secrets.COOLIFY_WEBHOOK }}
|
||||
COOLIFY_TOKEN: ${{ secrets.COOLIFY_TOKEN }}
|
||||
COOLIFY_AUTO_DEPLOY: ${{ vars.COOLIFY_AUTO_DEPLOY }}
|
||||
run: |
|
||||
mode="${COOLIFY_AUTO_DEPLOY:-}"
|
||||
if [ "$mode" = "off" ]; then
|
||||
echo "::notice::COOLIFY_AUTO_DEPLOY=off — skipping registry deploy hook. Maintainer uses the host-side SSH recipe instead."
|
||||
exit 0
|
||||
fi
|
||||
if [ -z "$COOLIFY_WEBHOOK" ] || [ -z "$COOLIFY_TOKEN" ]; then
|
||||
echo "::warning::COOLIFY_WEBHOOK or COOLIFY_TOKEN secret missing — skipping auto-deploy. Configure in repo Settings → Secrets to enable."
|
||||
if [ "$mode" = "on" ]; then
|
||||
echo "::error::COOLIFY_AUTO_DEPLOY=on but COOLIFY_WEBHOOK or COOLIFY_TOKEN secret is missing. Set both under repo Settings → Secrets and variables → Actions, or set COOLIFY_AUTO_DEPLOY=off to silence."
|
||||
exit 1
|
||||
fi
|
||||
echo "::warning::COOLIFY_WEBHOOK or COOLIFY_TOKEN secret missing — skipping auto-deploy. Configure in repo Settings → Secrets to enable, or set the COOLIFY_AUTO_DEPLOY variable to 'off' to silence this warning."
|
||||
exit 0
|
||||
fi
|
||||
# v1.4.22 C3 — force=true tells Coolify to skip its image-cache
|
||||
|
||||
@@ -41,20 +41,18 @@ jobs:
|
||||
- name: Unit tests
|
||||
run: pnpm test
|
||||
|
||||
# v1.4.23 W4 F5c — OpenAPI spec drift check.
|
||||
# OpenAPI spec drift gate — hard-fail (flipped from warn-only in v1.4.25).
|
||||
#
|
||||
# `continue-on-error: true` is intentional for v1.4.23: the
|
||||
# registry covers only the iOS-touched routes today, and the
|
||||
# legacy hand-maintained spec at docs/api/openapi-v1422-legacy.yaml
|
||||
# carries the rest. Once the registry hits ~95% coverage in
|
||||
# v1.4.24+, flip this to a hard gate by removing
|
||||
# `continue-on-error`.
|
||||
# The Zod registry is the source of truth for the public API contract.
|
||||
# Any route signature change must be reflected in `docs/api/openapi.yaml`
|
||||
# so downstream consumers (notably the v1.5 iOS Swift codegen) stay in
|
||||
# lockstep. If this step fails, run `pnpm openapi:generate` locally and
|
||||
# commit the regenerated spec.
|
||||
- name: OpenAPI spec drift check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
pnpm openapi:generate
|
||||
if ! git diff --exit-code docs/api/openapi.yaml; then
|
||||
echo "::warning::OpenAPI spec out of date. Run 'pnpm openapi:generate' locally and commit the changes."
|
||||
echo "::error::OpenAPI drift detected — regenerate the spec via 'pnpm openapi:generate' and commit the result. The Zod registry under src/lib/openapi/ is the source of truth for the public API contract."
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::OpenAPI spec in sync with Zod source schemas."
|
||||
|
||||
Reference in new issue
Block a user