From 4f77abc1575f4c50fc911751ad4134580281a194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 28 Aug 2026 22:02:20 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=20v1.37.32=20=E2=80=94=20Korean?= =?UTF-8?q?=20joins=20as=20a=20seventh=20interface=20language?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrates @aucun6352's Korean locale (PR #839): the full catalog, the NFC recompose in the shared fold, the Hangul lab-name normaliser, the tilde range separator, and the officially published Korean PHQ-9, GAD-7 and WHO-5 wording. Resolves the CHANGELOG and OpenAPI conflicts against the current trunk and regenerates the spec. Raises the aggregate bundle budget to 3700 KB gz for the seventh message catalog, with the reasoning recorded next to the value. The contributor's three commits are preserved with their authorship. --- CHANGELOG.md | 14 ++++++++++++++ bundle-budget.json | 4 ++-- docs/api/openapi.yaml | 2 +- messages/ko.json | 39 +++++++++++++++++++++++++++++---------- package.json | 2 +- public/sw.js | 2 +- 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2087c9e61..fdee46a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.37.32] — 2026-08-28 + +Korean joins as a seventh interface language, contributed by @aucun6352, and the three things that had to be fixed before it could actually work. + +### Added + +- Korean (`ko`) ships as a seventh interface language, AI-initial and community-maintained alongside French, Spanish, Italian and Polish. The whole surface speaks it: dashboard, settings, share views, the clinician report, notifications and the AI prompts. The PHQ-9, GAD-7 and WHO-5 questionnaires carry their officially published Korean wording so a score stays comparable to the Korean literature; the sleep questionnaire stays in its English-validated form with an honest note. The Coach and the daily briefing take the reviewed English body with a Korean reply directive rather than a hand-curated Korean one, the same route the other community locales took. + +### Fixed + +- A matcher that reads app text against text a person or a lab typed matched nothing at all in Korean. The shared fold decomposes with NFD and strips the combining marks, which turns Latin accents into plain letters, but Hangul decomposes into jamo, and jamo are not combining marks, so they survived and the folded text no longer resembled any table the app carries. Both readers of that fold were affected: the normative-claim check that grades whether a recommendation needs a citation, and the parser that reads a printed reference range. The fold recomposes now, and every Latin, Polish and German fold is byte-identical to before. +- No Korean analyte name could resolve to a LOINC code. The key normaliser kept `[a-z0-9]` and dropped everything else, so every Korean lab name in the catalogue normalised to the empty string and the derived index held none of them, and a Korean account's cholesterol result reached a FHIR export uncoded. +- The reference-range parser now reads the tilde a Korean lab prints (`3.5~5.0`), in both the ASCII and fullwidth forms. It had the hyphen and the dashes and not the separator the reports actually use. + ## [1.37.31] — 2026-08-27 The retry ledger knew when an AI provider was failing; the operator had no way to see it. diff --git a/bundle-budget.json b/bundle-budget.json index c0df0e9d6..868b80aa6 100644 --- a/bundle-budget.json +++ b/bundle-budget.json @@ -1,11 +1,11 @@ { - "$comment": "Client-bundle budgets, enforced by scripts/check-bundle-budget.mjs (--check) after `next build` in CI. Values are KB gzip and include every emitted lazy route and locale chunk, while route budgets include the shared rootMainFiles baseline. Re-measured at v1.37.2: / 455, /insights 443, /measurements 441, /insights/mood 453, total 3328, baseline 130, lazy catalogs 849. Every per-route ceiling is unchanged. The /insights overview crept a kilobyte over its 445 ceiling when the ECG teaser began importing its overview cap from the classification module, dragging that module's label map and helpers into the eager page; the cap now lives in its own value-only module, and the Health Score's single provenance entry is split out of the full metric-provenance map, so the overview imports the bare number and citation it renders rather than every metric's label map behind them. Both are back under 445 with room to spare. The 42 KB aggregate rise from 3286 (v1.35.0) is all locale-catalog strings: v1.36 and v1.37 added the encounter, mood-context, mood-prognosis, practitioner, workout-batch and account-sharing surfaces, and their copy lands across the six locale catalogs the whole-build sum counts independently (804 to 849 KB). The non-catalog client chunks are flat against v1.35.0, so nothing a person downloads on a given route grew beyond its own ceiling. The aggregate cap therefore moves from 3310 to 3352 (24 KB headroom, matching the previous margin); per-route ceilings, the static-catalog guard and the one-Recharts-chunk guard remain unchanged. v1.37.4's honest-error-state charge then added recoverable read-failure handling to thirteen surfaces (the shared QueryErrorCard and QueryErrorRow primitives plus an isError branch per surface) and rewrote the MCP scope note and the managed-record error copy across the six locales; the measured total rose to 3404 while every per-route ceiling stayed met (/page 457, /insights 445, /measurements 443, /insights/mood 455). The aggregate cap moves from 3352 to 3428, keeping the same margin; nothing a person downloads on a given route grew beyond its ceiling. v1.37.15's async-truth charge then added the refresh settling state with its two-locale-key copy, the index-outcome plain-words line (seven keys across the six catalogs) and the reindex-count wiring; the measured total rose to 3429 while every per-route ceiling stayed met (/page 456, /insights 445, /measurements 442, /insights/mood 455). The aggregate cap moves from 3428 to 3452, keeping the same margin; nothing a person downloads on a given route grew beyond its ceiling. v1.37.27 found /insights sitting a few hundred bytes under 445 on main and a few hundred over it on the release branch — the page had been at its ceiling for three releases and the release only had to breathe on it. The weight it was carrying was not its own: the authenticated shell imported one destination predicate from the `@/lib/record-settings` barrel, and the barrel also re-exports the managed settings-patch schemas, so Zod, the module registry and three validation modules rode into the eager bundle of every authenticated route for a function that reads a URL slug. The shell imports the `classification` leaf now. Measured across all 585 client-rendered routes: none grew by a byte, 100 fell, by 4.7 KB at the median and 8.1 KB at the most; /insights 444 to 437, /measurements 442 to 435, / 456 to 450, /insights/mood 455 to 450. The whole-build sum moved the other way, 3451 to 3476, and that direction is the metric rather than the change: the aggregate counts each emitted chunk once, so a graph that leaves the chunk group every route shares gets counted again wherever it lands — here inside the deferred settings chunks that actually use it. Bytes on disk rose 25 KB; bytes a reader downloads only ever fell. The aggregate cap moves from 3452 to 3500, the same 24 KB headroom as the four re-measurements before it. Per-route ceilings keep their old values rather than tightening to the new floor: the CI runner measures each route about 1.5 KB heavier than a local arm64 build does, and a ceiling set from a local number would fail on a build that had not regressed. The static-catalog guard and the one-Recharts-chunk guard remain unchanged.", + "$comment": "Client-bundle budgets, enforced by scripts/check-bundle-budget.mjs (--check) after `next build` in CI. Values are KB gzip and include every emitted lazy route and locale chunk, while route budgets include the shared rootMainFiles baseline. Re-measured at v1.37.2: / 455, /insights 443, /measurements 441, /insights/mood 453, total 3328, baseline 130, lazy catalogs 849. Every per-route ceiling is unchanged. The /insights overview crept a kilobyte over its 445 ceiling when the ECG teaser began importing its overview cap from the classification module, dragging that module's label map and helpers into the eager page; the cap now lives in its own value-only module, and the Health Score's single provenance entry is split out of the full metric-provenance map, so the overview imports the bare number and citation it renders rather than every metric's label map behind them. Both are back under 445 with room to spare. The 42 KB aggregate rise from 3286 (v1.35.0) is all locale-catalog strings: v1.36 and v1.37 added the encounter, mood-context, mood-prognosis, practitioner, workout-batch and account-sharing surfaces, and their copy lands across the six locale catalogs the whole-build sum counts independently (804 to 849 KB). The non-catalog client chunks are flat against v1.35.0, so nothing a person downloads on a given route grew beyond its own ceiling. The aggregate cap therefore moves from 3310 to 3352 (24 KB headroom, matching the previous margin); per-route ceilings, the static-catalog guard and the one-Recharts-chunk guard remain unchanged. v1.37.4's honest-error-state charge then added recoverable read-failure handling to thirteen surfaces (the shared QueryErrorCard and QueryErrorRow primitives plus an isError branch per surface) and rewrote the MCP scope note and the managed-record error copy across the six locales; the measured total rose to 3404 while every per-route ceiling stayed met (/page 457, /insights 445, /measurements 443, /insights/mood 455). The aggregate cap moves from 3352 to 3428, keeping the same margin; nothing a person downloads on a given route grew beyond its ceiling. v1.37.15's async-truth charge then added the refresh settling state with its two-locale-key copy, the index-outcome plain-words line (seven keys across the six catalogs) and the reindex-count wiring; the measured total rose to 3429 while every per-route ceiling stayed met (/page 456, /insights 445, /measurements 442, /insights/mood 455). The aggregate cap moves from 3428 to 3452, keeping the same margin; nothing a person downloads on a given route grew beyond its ceiling. v1.37.27 found /insights sitting a few hundred bytes under 445 on main and a few hundred over it on the release branch \u2014 the page had been at its ceiling for three releases and the release only had to breathe on it. The weight it was carrying was not its own: the authenticated shell imported one destination predicate from the `@/lib/record-settings` barrel, and the barrel also re-exports the managed settings-patch schemas, so Zod, the module registry and three validation modules rode into the eager bundle of every authenticated route for a function that reads a URL slug. The shell imports the `classification` leaf now. Measured across all 585 client-rendered routes: none grew by a byte, 100 fell, by 4.7 KB at the median and 8.1 KB at the most; /insights 444 to 437, /measurements 442 to 435, / 456 to 450, /insights/mood 455 to 450. The whole-build sum moved the other way, 3451 to 3476, and that direction is the metric rather than the change: the aggregate counts each emitted chunk once, so a graph that leaves the chunk group every route shares gets counted again wherever it lands \u2014 here inside the deferred settings chunks that actually use it. Bytes on disk rose 25 KB; bytes a reader downloads only ever fell. The aggregate cap moves from 3452 to 3500, the same 24 KB headroom as the four re-measurements before it. Per-route ceilings keep their old values rather than tightening to the new floor: the CI runner measures each route about 1.5 KB heavier than a local arm64 build does, and a ceiling set from a local number would fail on a build that had not regressed. The static-catalog guard and the one-Recharts-chunk guard remain unchanged. Raised to 3700 at v1.37.32 for the seventh interface language (Korean): the lazy message catalogs went 6->7 chunks (1026 KB gz measured), pushing the aggregate to 3631 against the old 3500. The raise is entirely the new catalog; no route budget moved, and the four eager route ceilings are unchanged and still passing. Most of the aggregate is the dynamic locale-switch and fallback path that a first paint never loads (see issue #253), so this ceiling guards bytes almost no visitor requests; 3700 leaves ~69 KB for ordinary drift until that double-count is cleaned up.", "routesKbGz": { "/page": 460, "/insights/page": 445, "/measurements/page": 445, "/insights/mood/page": 460 }, - "totalClientKbGz": 3500, + "totalClientKbGz": 3700, "maxRechartsChunks": 1 } diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 3cb4301c2..c3e95bdfe 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: HealthLog API - version: 1.37.31 + version: 1.37.32 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. diff --git a/messages/ko.json b/messages/ko.json index 8270f7797..11d30c9fb 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -2198,14 +2198,6 @@ "noData": "선택한 기간에 데이터가 없어요", "bucketWeekly": "주 평균", "bucketMonthly": "월 평균", - "points7Label": "7개", - "points7Title": "최근 데이터 7개", - "points30Label": "30개", - "points30Title": "최근 데이터 30개", - "points90Label": "90개", - "points90Title": "최근 데이터 90개", - "pointsAllLabel": "전체", - "pointsAllTitle": "사용 가능한 데이터 전체", "systolic": "수축기", "diastolic": "이완기", "bodyWater": "체수분", @@ -2294,7 +2286,15 @@ "moodHeatmapEmpty": "기분 히트맵, 아직 기록한 날이 없어요.", "complianceHeatmap": "복약 순응도 히트맵, {start}부터 {end}까지, 활동일 {days}일.", "complianceHeatmapEmpty": "복약 순응도 히트맵, 아직 활동일이 없어요." - } + }, + "days7Label": "7일", + "days7Title": "최근 7일", + "days30Label": "30일", + "days30Title": "최근 30일", + "days90Label": "90일", + "days90Title": "최근 90일", + "daysAllLabel": "전체", + "daysAllTitle": "전체 기간" }, "chart": { "overlay": { @@ -7029,7 +7029,26 @@ "countOther": "실패 {count}건", "lastFailed": "마지막 실패 {when}" }, - "deleteAllDetail": "사용자 계정은 그대로 남아요." + "deleteAllDetail": "사용자 계정은 그대로 남아요.", + "providerHealth": { + "title": "AI 제공자 상태", + "description": "사용자별 재시도 기록에서 집계한 각 AI 제공자의 전달 상태입니다.", + "loading": "제공자 상태를 불러오는 중…", + "loadError": "제공자 상태를 불러올 수 없습니다.", + "empty": "아직 기록된 AI 호출이 없습니다.", + "trackedUsers": "{count}명 사용자 추적됨", + "lastOk": "마지막 성공 {at}", + "lastFailure": "마지막 실패 {at}", + "statusHealthy": "정상", + "statusFailing": "{tracked}개 중 {failing}개 실패, {run}회 연속", + "typeAdminOpenai": "서버 키 (운영자)", + "typeAdminCodex": "중앙 ChatGPT (운영자)", + "typeOpenai": "OpenAI 키 (개인)", + "typeAnthropic": "Anthropic 키 (개인)", + "typeCodex": "ChatGPT 로그인 (개인)", + "typeLocal": "로컬 모델 (개인)", + "typeGateway": "OpenAI 호환 게이트웨이 (개인)" + } }, "achievements": { "title": "업적", diff --git a/package.json b/package.json index 8cb76cb1f..b7f871096 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "healthlog", - "version": "1.37.31", + "version": "1.37.32", "description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.", "license": "PolyForm-Noncommercial-1.0.0", "homepage": "https://healthlog.dev", diff --git a/public/sw.js b/public/sw.js index 21c549380..9a8981d88 100644 --- a/public/sw.js +++ b/public/sw.js @@ -36,7 +36,7 @@ try { // v1.4.38.4 → v1.4.42. Do not hand-edit; bump `package.json` and rebuild. const CACHE_VERSION = (typeof self !== "undefined" && self.__APP_VERSION__) || - /* @sw-version-fallback */ "v1.37.31"; + /* @sw-version-fallback */ "v1.37.32"; const STATIC_CACHE = `healthlog-static-${CACHE_VERSION}`; const PAGE_CACHE = `healthlog-pages-${CACHE_VERSION}`; // v1.18.6 — read-only data cache for a curated allowlist of safe GET `/api/*`