diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a81df28..d5274dc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,6 +25,8 @@ Dependencies are managed through normal Go module resolution. Review dependency
DB-backed integration tests skip when no Postgres connection is configured. To run them, provide libpq environment variables or `READINESS_TEST_DSN`. Tests create throwaway schemas and drop them during cleanup.
+See `docs/TEST_COVERAGE.md` for the current focused coverage roadmap and fixture rules.
+
### CI and race detector policy
The default required-looking CI job is `Build, Vet & Test`. Keep that job name stable unless repository branch protection is updated at the same time.
diff --git a/docs/TEST_COVERAGE.md b/docs/TEST_COVERAGE.md
new file mode 100644
index 0000000..50e4dd3
--- /dev/null
+++ b/docs/TEST_COVERAGE.md
@@ -0,0 +1,35 @@
+# Focused Test Coverage Roadmap
+
+This roadmap tracks high-value Health Dashboard test coverage without turning the suite into a slow infrastructure exercise.
+
+## Constraints
+
+- Default CI must stay pure and fast under `go test ./...`.
+- Fixtures must be synthetic or anonymized. Do not commit personal health exports, screenshots, logs, or raw metric dumps.
+- DB-backed tests should skip cleanly unless a Postgres connection is configured.
+- Coverage thresholds are intentionally deferred until the active product contracts have a useful baseline.
+
+## Current Coverage Map
+
+| Area | Current state | Next useful coverage |
+|---|---|---|
+| `internal/health` formulas | Strong coverage for readiness, evidence payloads, EnergyBank v2, stress, i18n labels, and anomaly helpers. | Add tests only when formula or evidence contracts change. |
+| Apple Health import | Synthetic XML fixtures cover mapped records, empty input, malformed input, sleep-stage mapping, and focused parser edge cases. | Add fixtures for real bug classes after reducing them to minimal synthetic XML. |
+| UI and admin APIs | Contract tests cover admin pages, auth/session behavior, webhook dispatch, dashboard sections, and tenant scope. | Pin response-shape changes before frontend code starts depending on them. |
+| Storage writers | Readiness redesign, EnergyBank, sleep gates, freshness, calibration, and tenant helpers have focused tests, with DB-backed tests gated. | Prefer bug-driven storage contract tests over broad repository-level sweeps. |
+| Notifications | Morning/evening report, freshness banners, smart retry, Telegram webhook, and proactive framework tests cover key behavior. | Add regression tests when notification timing or skip conditions change. |
+| CI policy | Default CI runs build, vet, and pure tests; race detector is manual and documented. | Keep race detector separate from default PR checks unless the runtime cost becomes acceptable. |
+
+## Delivered First Batch
+
+Issue #149 starts with a small Apple Health import safety batch:
+
+- `internal/applehealth/testdata/focused_edge_export.xml` is a synthetic fixture with no personal data.
+- `TestParseXMLFocusedEdgeFixturePinsImportSafety` checks percent normalization boundaries, duration-derived category metrics, stand-hour mapping, unknown quantity fallback, invalid duration rejection, and unsupported correlation skipping.
+
+## Candidate Follow-Ups
+
+- Dashboard API response stability tests for fields consumed by `static/app.js` and `static/charts.js`.
+- Import ZIP wrapper tests around missing `export.xml`, progress status, and parse error propagation.
+- Storage contract tests for freshness/readiness behavior when a bug or product change proves the need.
+- One informational `go test -cover` baseline after the focused lanes are less sparse, without enforcing a threshold.
diff --git a/docs/ai-plans/2026-06-06-issue-149-focused-test-coverage-roadmap.html b/docs/ai-plans/2026-06-06-issue-149-focused-test-coverage-roadmap.html
new file mode 100644
index 0000000..b258841
--- /dev/null
+++ b/docs/ai-plans/2026-06-06-issue-149-focused-test-coverage-roadmap.html
@@ -0,0 +1,162 @@
+
+
+
+
+ Implementation Plan: Issue #149 Focused Test Coverage Roadmap
+
+
+
+
+
+ Implementation Plan: Issue #149 Focused Test Coverage Roadmap
+ Approved and implemented
+
+
+ Task Summary
+ Create a current, focused test coverage roadmap for active Health Dashboard behavior. This should update the issue/doc roadmap and add only high-value missing tests, not introduce broad coverage thresholds or slow infrastructure.
+
+
+
+ Current Behavior
+
+ - The repository currently has 78 Go test files under
internal and related packages.
+ - Existing tests already cover major active areas: health formulas, readiness evidence, EnergyBank v2, stress validation, Apple Health XML parser fixtures, UI admin contracts, tenant scope, webhook dispatch, and storage writers.
+ internal/applehealth/testdata already has synthetic, malformed, and empty XML fixtures.
+ CONTRIBUTING.md states DB-backed tests skip when no Postgres connection is configured and warns against real personal health data in fixtures.
+ - CI currently runs the full pure test suite under
go test ./....
+
+
+
+
+ Desired Behavior
+
+ - The repo has a short, current roadmap that identifies remaining high-value test gaps instead of implying the test suite is empty.
+ - New tests target product contracts that change often: readiness freshness, EnergyBank v1/v2 compatibility boundaries, import conflict resolution, and API response stability.
+ - All tests added in this lane run under the existing default
go test ./... path unless explicitly DB-gated and justified.
+ - No real personal health data is committed.
+
+
+
+
+ Assumptions And Unknowns
+
+ - Assumption: this issue is best handled as a roadmap plus a small first batch of tests, not a large test sweep.
+ - Assumption: issue #166 should own readiness freshness contract tests if implemented first.
+ - Unknown: whether the user wants child GitHub issues created for each coverage lane or a single living doc section.
+ - Unknown: whether coverage measurement should be introduced now; the issue says threshold should wait until there is a meaningful baseline.
+
+
+
+
+ Files Likely To Change
+
+ docs/ARCHITECTURE.md, README.md, or CONTRIBUTING.md - add the current test coverage roadmap.
+ internal/health/*_test.go - add focused invariant tests only where gaps remain.
+ internal/applehealth/testdata/* and internal/applehealth/parse_test.go - add a small anonymized fixture only if current fixtures do not cover a real parser risk.
+ internal/ui/*_test.go - add API response contract tests if readiness/AI/dashboard shapes are changed.
+ - GitHub issue comments or child issues - optional, if the roadmap should live in GitHub rather than docs.
+
+
+
+
+ Implementation Steps
+
+ - Produce a compact test inventory grouped by package and behavior.
+ - Map the issue's candidate lanes to current coverage and mark each as covered, partial, or missing.
+ - Choose a first small batch of tests that protects active product behavior without DB/network requirements.
+ - Add or update docs with the roadmap and "do not commit personal health data" fixture rule.
+ - If GitHub child issues are desired, create them after user approval with clear scopes and dependencies.
+ - Run the default test suite and vet.
+
+
+
+
+ Impact
+
+ - CI: should remain fast; no coverage threshold or race job in this issue.
+ - Data: no production DB changes; fixtures must stay synthetic/anonymized.
+ - Product behavior: tests should pin existing behavior unless paired with an approved product change.
+ - GitHub process: optional child issues can make the roadmap easier to execute incrementally.
+
+
+
+
+ Test Plan
+
+ go test ./internal/health ./internal/applehealth ./internal/ui ./internal/storage -count=1
+ go test ./...
+ go vet ./...
+ - If fixtures are added: manually inspect fixture content for personal data before commit.
+
+
+
+
+ Risks And Edge Cases
+
+ - A generic coverage push can create brittle tests that slow useful development.
+ - DB-backed tests can become flaky if they assume a developer has a local database; keep them opt-in unless necessary.
+ - Fixtures can accidentally leak personal data; prefer minimal synthetic XML.
+ - This issue overlaps with #166 and #152; keep readiness contract tests and race policy in their respective issues where possible.
+
+
+
+
+ Rollback Plan
+ Revert roadmap docs and any added tests. If child issues are created incorrectly, close or relabel them rather than deleting historical context.
+
+
+
+ Open Questions
+
+ - Should the roadmap live in docs, GitHub child issues, or both?
+ - Which first coverage batch is most valuable after #166: import safety, EnergyBank compatibility, or dashboard API stability?
+ - Should coverage metrics be measured once as an informational baseline, without enforcing a threshold?
+
+
+
+
+ Approval Gate
+ Approved by the user on 2026-06-06 before production code changes.
+
+
+
+ Implementation Result
+
+ - Added
docs/TEST_COVERAGE.md as the living focused coverage roadmap.
+ - Linked the roadmap from
CONTRIBUTING.md.
+ - Added a synthetic Apple Health edge fixture at
internal/applehealth/testdata/focused_edge_export.xml.
+ - Added
TestParseXMLFocusedEdgeFixturePinsImportSafety to pin percent normalization boundaries, duration-derived category metrics, stand-hour mapping, unknown quantity fallback, invalid duration rejection, and unsupported correlation skipping.
+ - No real personal health data was added.
+
+
+
+
+ Verification Run
+
+ go test ./internal/health ./internal/applehealth ./internal/ui ./internal/storage -count=1
+ go test ./...
+ go vet ./...
+ git diff --check
+
+
+
+
+ Known Limitations And Follow-Up
+
+ - This PR intentionally does not add a coverage threshold.
+ - DB-backed coverage remains opt-in unless a product bug requires a focused storage contract test.
+ - Candidate follow-ups are tracked in
docs/TEST_COVERAGE.md rather than created as child issues in this first pass.
+
+
+
+
+
diff --git a/internal/applehealth/parse_test.go b/internal/applehealth/parse_test.go
index dc256eb..6ea2331 100644
--- a/internal/applehealth/parse_test.go
+++ b/internal/applehealth/parse_test.go
@@ -26,6 +26,21 @@ func TestParseXMLSyntheticFixtureMapsHealthKitRecords(t *testing.T) {
assertNoPoint(t, points, "sleep_total", "2026-01-02 02:00:00 +0000")
}
+func TestParseXMLFocusedEdgeFixturePinsImportSafety(t *testing.T) {
+ points := collectXMLFixturePoints(t, "testdata/focused_edge_export.xml")
+
+ if got, want := len(points), 4; got != want {
+ t.Fatalf("point count = %d, want %d: %+v", got, want, points)
+ }
+ assertPoint(t, points, "blood_oxygen_saturation", "%", "2026-02-01 06:00:00 +0100", "Synthetic Watch", 98)
+ assertPoint(t, points, "mindful_minutes", "min", "2026-02-01 09:00:00 +0100", "Synthetic Phone", 15)
+ assertPoint(t, points, "apple_stand_hour", "count", "2026-02-01 10:00:00 +0100", "Synthetic Watch", 1)
+ assertPoint(t, points, "some_new_metric", "count", "2026-02-01 13:00:00 +0100", "Synthetic Device", 12.5)
+
+ assertMetricCount(t, points, "mindful_minutes", 1)
+ assertNoMetric(t, points, "blood_pressure")
+}
+
func TestParseXMLEmptyAndMalformedInputs(t *testing.T) {
var emitted bool
if err := parseXMLFixture("testdata/empty_export.xml", func(points []storage.MetricPoint) {
@@ -87,6 +102,15 @@ func assertNoPoint(t *testing.T, points []storage.MetricPoint, metric, date stri
}
}
+func assertNoMetric(t *testing.T, points []storage.MetricPoint, metric string) {
+ t.Helper()
+ for _, p := range points {
+ if p.MetricName == metric {
+ t.Fatalf("unexpected metric=%s point: %+v", metric, p)
+ }
+ }
+}
+
func assertMetricCount(t *testing.T, points []storage.MetricPoint, metric string, want int) {
t.Helper()
got := 0
diff --git a/internal/applehealth/testdata/focused_edge_export.xml b/internal/applehealth/testdata/focused_edge_export.xml
new file mode 100644
index 0000000..b8b75e0
--- /dev/null
+++ b/internal/applehealth/testdata/focused_edge_export.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+