- one engine: the dimension series is a sibling calculator of the mood
aggregate hub and rides the same bounded read, so no second summary of
mood can disagree with the first
- 7 / 30 / 90-day means per dimension, plus the daily series behind them
- a dimension nobody answered is named as not recorded rather than drawn
flat through the middle of the scale, and a zero counts as an answer
- each line keeps the orientation the person answered in; the legend
says which way the inverse one runs
- the card rides the existing module-gated aggregate read and query key
git, eslint and vitest each carry an exclusion for local development
worktrees, and all three name /.worktrees/ while the convention actually in
use is /.wt-*/. Every one of them was therefore walking eleven full copies of
the source tree.
The consequences differed by tool and none of them announced itself. pnpm lint
took ten minutes instead of one and reported warnings from whatever commits
those copies happened to sit on. vitest collected duplicate suites that drag in
a second React instance, which fails in ways that have nothing to do with the
working tree. git left them merely untracked, one git add -A away from
committing the repository eleven times over.
With the true warning count finally visible, teach no-unused-vars the
underscore convention this codebase already follows and drop the five
disable directives that convention made redundant. pnpm lint is clean.
A non-underscore unused argument still warns; that was checked rather than
assumed.
All 104 bindings across the five registrars now go through createAndWork,
whose handler type is (jobs) => Promise<JobOutcome>. The forty-two inline
handlers return what they did. A handler that falls off the end no longer
compiles, and the healthlog/job-handler-outcome rule refuses a bare
boss.work( anywhere under src/lib/jobs except the one module that owns
the wrapper. The rule's header states its own limit: it catches the
omission, not a handler that claims ok:true after a swallow.
Retry policy, reviewed queue by queue because the rethrow changes what
runs, not only what is visible:
- Twenty-one cron passes carry cronIsTheRetry (retryLimit 0). They are
the retention DELETEs, the OAuth-state prunes, the hourly maintenance
sweeps and the nightly per-sample drain — every one of them a
statement that will fail identically a second later. pg-boss defaults
to two retries with no delay, so converting them without this would
have run the same doomed statement three times a night. The next tick
is the retry and it arrives with the whole gap as backoff.
- The policy rides on the schedule, not on createQueue: create_queue()
ends in ON CONFLICT DO NOTHING, so a queue-level default would be
inert on every already-running instance.
- Everything else keeps its retries deliberately. A TLS probe, an
off-host upload, a restore drill and a provider poll all fail
transiently, and the reminder ticks only fail at job level when the
pass itself could not run.
The detail surface at /insights/workouts/[id] gains a real heart-rate
curve, effort zones, an aspect-true GPS route, and per-kilometre splits
— all read from data already stored, no new integration, no migration.
- HR curve: one server path with strict provenance — stored
WorkoutSamples series first, a raw-PULSE window reconstruction second
(quality-gated so sparse data hides rather than lies), nothing third.
Adaptive bucketing keeps on-screen density constant across a 20-minute
HIIT and a 6-hour ride; a min/max envelope surfaces intervals.
- Effort zones: five %HRmax bands via the Tanaka HRmax, with a device's
own reported time-in-zone winning when present. Descriptive only.
- GPS route: self-rendered inline SVG with an equirectangular
cos(midLat) longitude correction (fixes the ~40% horizontal stretch),
aspect-clamped viewBox, RDP downsampling, start/end markers, and an
optional elevation profile. Nothing leaves the origin — no map tiles.
- Per-km splits and a GPX export, both derived client/server-side from
the geometry already on the payload.
- The endpoint extends additively (hrSeries, zones, splits, sportContext,
a reserved insight seam) behind an opt-in compact=1 param that drops
the raw sample/timestamp blobs for the web client; without the param
the response is unchanged.
- Every data-less section now hides instead of rendering an empty card;
the growing detail component is split into a workout-detail/ directory.
- List rows flag which sessions open into a rich detail.
Today hero's top-signal delta used opacity-on-muted text, landing below
AA on the app's most prominent card; five mood charts and the sleep-
stage IN_BED fill painted raw --dracula-*/hardcoded-hex hues with no
(or an unused) light-theme counterpart. Route all of them through
existing semantic/chart tokens instead (destructive/warning/success,
chart-1..5, and a newly minted --chart-inbed with its own Alucard
override) so every hue is AA-tuned per theme.
Also collapses three duplicated primitives the audit flagged: the
click-Popover VitalsInfoTip and hover-Tooltip InfoHint merge into one
touch-first ui/info-popover; MedicationViewToggle and ModuleViewToggle
were verbatim copy-paste and now wrap one shared ui/view-toggle; the
triplicated sport-icon map (already drifted — mixedCardio rendered a
different glyph in the list vs the detail/dashboard views) moves to
lib/workouts/sport-icons.ts with HeartPulse as the settled icon.
Adds a no-restricted-imports lint rule banning direct recharts imports
outside the chart-runtime barrel, catching the bundle-duplication
mistake at local lint instead of PR-CI.
Verified UI-consistency findings from the campaign's design audit, each
pixel-faithful except the intended correction:
- insights hero band: off-scale py-5/gap-5 → the standard scale; H1
font-semibold → font-bold; sm:text-[28px] → sm:text-3xl.
- medication-compliance-chart + recent-achievements-card: hand-rolled
muted h-4 icon + text-sm headers → the standard TileHeader primitive so
they match sibling tiles.
- achievements stat tiles + clinician-view shells: p-5 → p-4 md:p-6.
- two text-primary header glyphs (research-mode dialog, enroll-mfa) →
text-foreground (never accent on a header glyph); the deliberate
badge-boxed icons left untouched.
- SheetHeader now reserves the trailing close-X gutter like DialogHeader,
closing the latent overlap class for future raw-Sheet consumers.
- five raw Dialogs on phone-reachable surfaces → ResponsiveSheet.
- new bg-card-scoped healthlog/spacing-scale lint (flags p-5/gap-5 etc.
only on card shells; list-marker insets + half-steps excluded) at error,
with the five offenders it flagged swept.
- dropped the stale CLAUDE.md '18 PascalCase outliers' line (0 remain).
Every data-slot preserved. No migration.
Migrate every remaining *-dracula-* utility site (127 lint warnings,
193 class occurrences) onto the semantic vocabulary. The swaps are
pixel-identical in both themes — each target token aliases the same
primitive the utility read, including the light-theme AA overrides:
- green/orange/cyan/red → success/warning/info/destructive
- purple → primary (same value in both themes)
- pink → the new --brand-pink token (Coach gradient, Apple-Health
badge, recording mic): dark #ff79c6 — 6.86:1 card / 5.97:1 bg;
light #a3134d — 7.32:1 card / 6.83:1 bg (computed)
- yellow sites resolve to their status meaning: warn-level log icon,
update-available pill and offline-geo hint → warning; the admin
helpful-rate ramp → success/warning/destructive; the confidence
meter mirrors the coverage-meter precedent (medium + low share the
caution tint, the lit count carries the distinction)
- the confidence-badge low tier dropped dead *-dracula-comment/*
classes (no generated utility) for border-border + bg-muted/50
Close two rule blind spots the sweep surfaced — directional borders
(border-l-dracula-red) and the parenthesised CSS-var shorthand
(bg-(--dracula-green)) — and flip healthlog/no-dracula-utility from
warn to error so the class cannot regrow. var(--dracula-*) references
in chart code stay: they are token references the light theme
overrides, and the chart palette is pinned visually.
Add healthlog/spacing-scale (error): pt-*/pb-* on CardHeader/CardContent
fights the gap-based Card contract — the header→body distance comes from
the parent flex gap, so slot padding stacks on top of it or is a no-op.
This is the drift class the earlier slot-padding sweep cleaned; the rule
keeps it from reaccumulating. Density belongs on the Card itself
(gap-2 py-3 md:py-4). No allowlist — an exception, should one ever be
load-bearing, carries an inline disable with its reason.
Remove the two stragglers the rule caught: pb-1 on the device-score tile
and pb-4 md:pb-6 on the intake history list, both stacking dead padding
on top of the card gap. RuleTester coverage for the modifier-prefixed,
arbitrary-value, cn()/ternary/template and member-expression forms.
The rule only matched stock-hue-plus-step utilities, letting three colour
escapes through CI: raw dracula-* utilities, hex/rgb/hsl/oklch literals in
colour-shaped JS props (the Recharts color/stroke/fill blind spot behind
the four hex chart colours just fixed), and Tailwind arbitrary-value
colours (bg-[#...]).
Add all three as selectable checks. color-props and arbitrary-value run at
error; the dracula check ships as a warning under a second registration
(healthlog/no-dracula-utility) because ~250 legacy sites predate the token
system — the light-theme overrides keep them readable, and the check moves
to error once the semantic sweep retires them (staged plan in the rule
header). Exempt: global-error.tsx (renders without the stylesheet), the
themeColor viewport metadata (matched by ancestry, not by file), test
files. Add a RuleTester suite covering all four detectors and the
exemptions.
Add the healthlog/no-raw-palette-color rule so UI code routes color
through the semantic tokens instead of Tailwind palette shades, and
sweep the remaining offenders (password-strength scale, injection-site
accents, redundant dark overrides). Add guard tests for width stability,
the single page-header vocabulary, and motion-reduce coverage on
indefinite animations.
Deleting a measurement or mood entry can be undone from the toast via
new restore endpoints, dialogs submit on enter with busy states, silent
mutations gain error toasts, and the settings area finishes its
consolidation: provider and integration cards live in focused
subcomponents, every setting persists server-side at exactly one home,
and the save conventions are written down. The remaining raw fetches in
settings move onto the shared client.
Blood-pressure fixtures in the in-target integration suite were copied
from live readings; they are now synthetic with the same in/out split so
every assertion holds. Hard-coded operator hostnames move behind env vars
(DEPLOY_LOGS_URL, HEALTHLOG_PROD_URL), the codex client identifies via
the public repository URL, the OpenAPI servers entry points at a neutral
example host, and remaining workflow, docs and comment references to the
operator's instance or working notes are generalised. The compose
whitelist also gains DEPLOY_WEBHOOK_SECRET, which was missing and made
the deploy webhook unusable on compose installs.
Pure-history medication detail page, legacy step consolidation, and the
finish of the outbound-fetch migration.
- /medications/[id] becomes a read-only history surface; every setting
moves into a new advanced-settings sheet, and the edit trigger becomes
a two-option picker. The list page is unchanged.
- A pg-boss job folds pre-v1.5.0 granular step rows into one canonical
daily total per user per day and soft-deletes the originals; migration
0087 adds a partial index for the converging discovery scan.
- Every remaining server-side fetch routes through safeFetch (Withings,
Codex OAuth + SSE, bug-report, Umami, GlitchTip, Loki); a new
healthlog/safe-fetch-required ESLint rule guards the boundary.
- Fixes: intake-edit dialog seeded from real values, consolidation skips
a unique-constraint day instead of stranding it, avatar upload reads
the body once under a bounded cap, egress lint rejects absolute URLs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
HealthLog — personal health-tracking PWA with weight, blood pressure,
pulse, mood tracking, medication compliance, Withings integration,
doctor report PDF export, and Dracula-themed UI.