Commit Graph
36 Commits
Author SHA1 Message Date
Marc-André Bombeck 3cf67d05f9 fix(ui): let the read-failure primitives decide by the error
About forty compact rows and seventy cards still offered a retry on a
spent read budget and drew an error card on a refusal, while a third
primitive (ReadFailureRow) did the right thing at eight places.

QueryErrorRow and QueryErrorCard now take the query's `error` and read
it through readFailureKind, which moves to src/lib/api next to
ApiError: a refusal (403) draws the surface's refused state or nothing,
a spent budget (429) says so without a retry, an ended session (401)
says so and offers sign-in instead of going blank, and only a real
failure offers the retry. ReadFailureRow is gone and every site passes
its error; a guard test fails on a site that does not.

The coach read strip keeps its card in the error state and holds one
body height across loading, failure and a one-line read.
2026-10-20 03:38:57 +02:00
Marc-André Bombeck 3e1f630fa1 Fix copy, colour and frame details across the record pages
- Achievements: '1 Tag', not '1 Tage'; point totals grouped; whole
  descriptions instead of a clamp that hid the target at the end.
- Illness: the episode note label is translated, day notes read in the
  content colour.
- Coach history: conversation titles read in the content colour.
- Anamnesis and mental wellbeing: drop a second page gutter the shell
  already provides, and the nested main landmark.
- Privacy: repair words broken by a stray hyphen and space.
- Cycle: the ring caption reads 'Tag 9 deines Zyklus' as one phrase.
- Practitioners: the add button says 'Hinzufügen' like every other page;
  a shorter search placeholder fits a phone.
- Empty state cards inset their content from their border, so a phone's
  full-width action no longer runs into the frame.
2026-10-08 16:34:54 +02:00
Marc-André Bombeck 7d25bb7352 fix(cycle): log past dates as themselves, in any order (#1004)
The log sheet took its "Day N" header and its "period ended" button
from the verdict, which is the answer about today. A January date opened
in September read "Day 2", and a back-dated period could only be closed
on dates where today happened to be a bleeding day, so the button came
and went as unrelated months were filled in.

The calendar read now carries, per date, the cycle day of the logged
cycle that date belongs to (null before the first start, after today, or
past the verdict's ceiling for an open cycle) and whether a one-tap
period end can land there. The sheet reads both off the grid day for the
date it opened on.

Saving a new entry with nothing picked used to write an empty row: the
request succeeded, the sheet closed and the calendar had nothing to
draw, which read as a save that did nothing. Save now needs something to
store and says what, and an emptied existing entry still saves. The
sheet also stops writing until the opened date's own entry has loaded,
because until then it held the previous date's row id and a quick Save
patched the wrong day.

On the server, a start recorded a few days after an earlier one is the
same period entered in the other order: opening the earlier start now
folds the later one into it instead of leaving a one-day cycle in front
of it, and a bleed too far before the next start to be its first day and
too close to be a cycle of its own no longer infers a start. Opening or
removing a start re-attributes the day-logs whose cycle changed.
2026-09-24 08:02:00 +02:00
Marc-André Bombeck e95612e3d3 fix(sharing): show a guardian the controls the routes accept (#939)
useRecordCapabilities answers per section now. canWriteDomain and
canManageDomain read the two lists the account payload publishes; in
one's own record they are always true, and under a refused, pending or
unproven context always false. canManage becomes the coarse switch,
true when any section is manageable, and is what the banner and the
navigation read.

Every add, edit and delete control names its own section: labs, mind,
profile, illness, cycle, medications, measurements, documents. Creates
that a WRITE route answers (visits, practitioners) ask canWriteDomain.
The generic delete button, the selection bar, the capture picker, the
dashboard quick entry and the bottom nav take the section from their
callers. Controls whose routes resolve the caller rather than the
record (settings shortcuts, chart overlay preferences, custom metrics,
the Today rail, OCR, efficacy targets, credential tabs) ask for the
caller's own record instead, and the customise-shortcut guard now
requires that shape.

The vault has no delegated write route, so its controls stay withheld
under every grant, and the documents page says so in one sentence
where the upload control would be, in all seven locales.
2026-09-09 19:42:27 +02:00
Marc-André Bombeck f63fdb373e fix(cycle): use the shared query-error primitives on failed cycle reads
The cycle tabs rendered a bespoke error row in muted text, wrapped in a
bare card. Replace it with the shared primitives: QueryErrorCard on the
standalone prediction/insights/settings tabs, and a borderless
QueryErrorRow inside the calendar card so the failure reads as foreground
content with retry rather than muted meta. Test watched the muted row
first.
2026-08-09 04:20:55 +02:00
Marc-André Bombeck bd6089734d fix(cycle): let the month grid read the month it is showing
The page reads one window anchored on today, ninety days back and half a
year forward, and the month grid navigates freely past it. A month
outside the window came back with no days: the cells still rendered
(they come from the month, not the read) and still opened the log sheet,
and the write behind it still landed, because the period route is built
for back-dating and re-anchors the neighbouring cycles for it. What did
not happen is anything visible. Saving an April flow in August closed
the sheet onto a month the read never carried, so the entry looked like
it had not been made, and entering it again did not help.

The grid now reports the month it moved to and gets its own bounded read
for it. The anchored window is untouched, because every other surface on
the page means today and the route caps a span at 400 days, so a union
with a month two years back would simply be refused.

A write also says so now. Saving, deleting and both period boundaries
raise a confirmation; the sheet's inline strip already named a failure.
Between the invisible month and the silent success there was no way to
tell a save that worked from one that never happened.

The sheet header carries the date it is writing to, and the one-tap
boundaries drop the word today when the day is not today. It always
wrote to the selected date; saying otherwise made a correction read as a
mistake.

The gear beside the log button is gone. It was a second door to the tab
directly below it and no other page carries one in that corner.

- calendar days carry isCycleStart, so the delete dialog can say that
  removing this day removes the period start with it
- window arithmetic is a pure module with its own tests, including the
  400-day span the anchor cannot be widened past
- integration: a day 111 back is written, is absent from the anchored
  read, and comes back complete on a window over its month
- new strings in all six languages
2026-08-07 15:20:29 +02:00
Marc-André Bombeck 7817632385 Hide the controls a delegate cannot use, surface by surface
Every page a switch can reach now renders the controls the grant covers and
none of the others. Readings, lab results and illness entries keep their add
path at WRITE and lose edit, delete and selection at both levels. Mood,
screeners, cycle days, checkup reminders and the document vault lose every
mutation control at both levels, because no grant admits those verbs and the
routes behind them refuse a switched session outright.

Absent, not disabled, throughout. The banner already says whose record this
is, so no chip repeats it beside each missing button; the one exception is
the measurements empty state, where somebody who may add will look for the
edit control beside the row they just entered, and one muted line says where
the limit is.

Two dead ends went with them: the custom-metric section on the measurements
page linked every row into a route the shell turns a delegate away from, and
the wrench glyphs point at settings pages sharing does not cover.
2026-08-03 12:11:42 +02:00
Marc-André Bombeck dbecbbcbdb Publish the resolved cycle verdict instead of letting each client work it out
The cycle ring's day count, its phase arcs, and the judgement that a period
is late all used to be worked out in web client code, including the
fourteen-day grace window that decides when the count stops being an
observed fact. Anything else that wanted the same answer had to reproduce
that rule, and a second copy of it is how a ring ends up showing someone a
cycle day their record does not hold.

GET /api/cycle/calendar now carries a verdict: the state, the cycle day,
the ring's arcs, the cycle start, how many days until the next period, and
when the period is late, how late. It is resolved against the user's own
timezone day, which a browser in another zone cannot do. The web wheel,
the wellness-strip tile, the insights teaser and the temperature chart all
render it, the grace constant is gone from the components directory, and
the spec says the field is required.

The overdue caption now names the number of days instead of only saying
later than usual.
2026-08-01 10:37:18 +02:00
Marc-André Bombeck 9f21d9f20d fix(cards): retire banned p-5 shells and accent header icons
Bring the structural card-anatomy offenders onto the spacing scale. The
cycle wellness-tile family (ring tile, phase-education, insight summary,
cycle view) and the insights wellness-scores tile drop banned p-5/px-5
py-5 for p-4 md:p-6 and keep their premium hue surface; their header and
info glyphs move off text-primary to foreground, spinners to muted. The
onboarding hand-rolled shells (anamnesis card, baseline form,
getting-started checklist, tour tooltip) drop p-5/space-y-5/rounded-2xl
for p-4 md:p-6/space-y-4/rounded-xl. settings/anamnesis-section moves its
three raw Card+CardTitle shells onto SettingsCard + SettingsCardHeader.
2026-07-09 12:05:34 +02:00
Marc-André Bombeck 23ded20306 fix(pages): replace bare auth-gate spinners with a layout-reserving skeleton
Introduce a shared PageAuthGate (header + tile skeleton) and adopt it on the
top-level pages that flashed a lone centered spinner while auth resolved:
mood, measurements, achievements, illness, mental-wellbeing, checkups, cycle,
and the labs / medications / custom-metrics / illness detail routes. The
auth-resolving frame now reserves the page geometry instead of snapping in
after a spinner.
2026-07-09 00:23:13 +02:00
Marc-André Bombeck e5ca125dfb fix(onboarding,integrations,cycle): deep-link source chips + cycle settings wrench
- Onboarding 'more sources' chips anchor to their provider card
  (/settings/integrations#oura etc.); connection cards gain scroll anchors.
  Apple Health (no web card) stays on the page root.
- Cycle module gains a header settings wrench for parity with labs /
  medications / illness; the tab strip is now controlled so the wrench jumps
  to the settings tab and ?tab= deep-links keep working.
2026-07-08 23:59:10 +02:00
Marc-André Bombeck e260b2d5c4 perf(charts): one shared async boundary for the whole recharts surface
Every next/dynamic chart import used to point at its own module, so
Turbopack emitted a separate chunk group per boundary — the production
build carried EIGHT ~312 KB copies of the recharts module graph, and
/insights (the worst-LCP route) downloaded two of them (~92 KB gz of
pure duplication, one fully unused at load).

All chart loaders now resolve the single chart-runtime barrel
(src/components/charts/chart-runtime.ts), which owns the only path to
recharts: one shared chunk group, recharts exists exactly once in the
build (verified: 8 -> 1 recharts-fingerprinted chunks), cached across
routes. Charts stay recharts and stay pixel-identical; every call site
keeps its own matching skeleton and ssr:false flag.

Riders of the same rule:
- the mood-trio barrel (mood-charts.ts, the v1.16.7 precedent this
  generalises) folds into the runtime and is removed
- the vitals sparkline body extracts to delta-sparkline.tsx and loads
  through the runtime; the tile keeps the fixed 40 px band, so the
  async gap is an identically-sized empty box
- /cycle's BBT chart (the last static recharts import on a live route)
  defers through the runtime with a card-matching skeleton

H3
2026-07-07 12:32:43 +02:00
Marc-André Bombeck 75b0e618b4 fix(cycle): stop the wheel counting past a plausible cycle day
A user-visible "day 90" inside the cycle ring traced to the open
cycle's phase window: the engine labels every day up to the predicted
next start, and a confirmed or anchored ovulation signal (BBT trend,
symptothermal double-check, LH surge — all clamped to future next
starts) or a long estimated length can push that prediction into the
future while the last logged period start is months old. Every day of
the gap then carries a population-framed LUTEAL label, and the wheel's
back-walk counted the whole gap as one giant cycle.

deriveWheelState now applies an honest ceiling: beyond the profile's
typical cycle length plus a 14-day grace window the wheel drops the
day count and phase claim, draws the canonical four-phase dial, and
the view swaps the caption for a factual "period is later than usual —
the count pauses" line plus a log-period CTA. The dashboard ring tile
and the insights summary card share the same derivation, so neither
can show the inflated count. Calendar labels and the server DTO are
unchanged.
2026-07-05 14:53:35 +02:00
Marc-André Bombeck 887c79ec86 refactor(ui): unify page headers and surface read failures
Add a PageHeader primitive (one H1 vocabulary, optional description,
back-link, and action slots) and route the hand-rolled headers through
it, so every module reads with the same title/subtitle rhythm and no
icon beside the page title. Mental Wellbeing gains a visible header.
Add a QueryErrorCard and wire the list surfaces so a failed read shows
a retry card instead of falling through to the empty state, and swap
their full-page spinners for skeletons that hold the layout.
2026-07-02 00:00:37 +02:00
Marc-André Bombeck 75129c798d refine(cycle): consolidate disclaimer, align columns, unify tiles
Move the non-medical disclaimer to the cycle-tracking on-ramp and drop it
from the repeating in-feature surfaces (calendar tab, predictions panel);
delete the now-unused disclaimer component. Align the left ring column with
the tab content rather than the tab strip. Raise the settings subheading
sizes to the standard card-description scale. Reflow the reminder rows so a
wrapping label no longer collides with its button at narrow widths. Unify
the wheel and phase-education tile padding. Cite the symptothermal
double-check primary source under the second fertility sign.
2026-06-21 10:57:16 +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 d2be72d57a feat(mobile): add scroll-touch momentum utility for horizontal strips
Add a `.scroll-touch` utility that restores iOS WebKit momentum
scrolling (`-webkit-overflow-scrolling: touch`) and contains the
overscroll chain so a flick at the end of a horizontal row does not
rubber-band the whole page. Apply it to the insights tab strip and the
cycle tab list — the two in-app horizontal scrollers that lost inertial
flick under the default `overflow-x-auto`.
2026-06-19 21:18:05 +02:00
Marc-André Bombeck 3ba542f797 fix(onboarding): make the guided tour first-time-only, drop every re-entry trigger
The guided tour now exists only as the first-time auto-start after onboarding.
Remove every in-app trigger that re-opened it: the per-module "show this tour"
button on the dashboard, measurements, mood, medications, labs, illness, cycle,
achievements, insights, and coach surfaces; and the Settings replay card.

Delete the now-orphaned ModuleTourTrigger component, the tour-restart worker,
the healthlog:module-tour and healthlog:tour-restart event listeners, the
single-stop overlay path, and the force-launch session marker. The first-time
auto-launch gate and the tour overlay itself are unchanged. Prune the orphaned
i18n keys across all locales and update the affected tests.
2026-06-18 23:58:27 +02:00
Marc-André Bombeck 33fec28b29 feat(onboarding): per-module tour re-entry + consolidated replay
Add a 'Diese Tour zeigen' affordance to every module header that opens the
tour narrowed to that module's card in place, plus additive data-tour
anchors (measurements/medications/labs/illness/vorsorge/cycle/mood/
insights/coach/integrations/export/achievements) so the cross-page walk
has a real target on each page. Consolidate the full-tour replay onto a
single Settings -> Advanced card, relabel it, and drop the duplicate from
About. The replay reset clears the stored progress so it restarts cleanly.
2026-06-18 19:49:37 +02:00
Marc-André Bombeck f3388ceb70 feat(cycle): redesign the log-day sheet — phase header, field info, wider dialog
Anchor the capture surface to the ring and lower its cognitive load:

- A hue-tinted phase-context strip (day-of-cycle + phase) at the top of the
  sheet, on the wellness-tile surface, honesty-gated the same way the phase-
  education card is — no phase claim until prediction is on, not raw-chart,
  and at least three cycles observed; otherwise a neutral day header.
- Inline field explainers (FieldInfo) on the clinical fields: BBT, cervical
  mucus, ovulation test, spotting, pregnancy/progesterone test, intercourse,
  protection — factual, descriptive-only, reachable by keyboard and screen
  reader.
- BBT is now the shadcn Input rather than a bare element, with a morning-
  timing hint, so it matches every other control.
- The symptothermal section (Temperature & ovulation) auto-expands for
  fertility goals and for an edited day that already holds a fertility sign,
  so a pre-filled value is never hidden behind a collapsed section.
- Desktop dialog widened to lg so the form breathes.

The payload builders and the form-hydration reconciliation are unchanged, so
the wire contract and their unit tests are untouched.
2026-06-16 21:32:58 +02:00
Marc-André Bombeck 3bbc0016d9 fix(cycle): restore the wellness-tile entrance so the ring renders
The wheel tile, phase-education card, and dashboard cycle tile each set
both data-revealed and .wellness-tile-rise on the same node. The keyframe
selector is [data-revealed="true"] .wellness-tile-rise (descendant
combinator), so it never matched a same-node pair — the tiles stayed at
the rise rule's opacity: 0, invisible but still boxed. Lift data-revealed
onto a contents wrapper (the documented pattern from the cycle insight
summary card) so the entrance plays and the signature ring paints.
2026-06-16 16:52:03 +02:00
Marc-André Bombeck 3f3b8cbf0c fix(ui): unify the add-entry pattern across feature pages and add the Vorsorge heading 2026-06-16 02:55:27 +02:00
Marc-André Bombeck f5798e20d8 fix(cycle): gate the calendar grid to a learning state during cold start
The predictions panel suppressed its fertile-window and ovulation rows while
fewer than three cycles were observed, but the month calendar did not — so a
brand-new user who logged a single period saw the grid paint a confident
fertile-window shading, a predicted-ovulation dot, and full phase bands off a
population 28/14 prior at ~0.20 confidence. Calendar-based fertility estimation
is unreliable below several recorded cycles, so the grid was asserting days it
could not know.

Thread the same still-learning gate into the calendar build: while fewer than
three cycles are observed, suppress the fertile window, the ovulation dot, and
the phase band (the predicted-period bar stays, mirroring the panel). Surface a
top-level stillLearning flag on the calendar response so the client renders a
calm "learning your cycle" banner over the grid instead of a bare, falsely
confident calendar. The flag is additive and back-compatible; older clients
ignore it and degrade to the previous render.

Also pin the observed-cycle count to a single source — the engine's
post-exclusion value — so the response never carries two divergent counts.
2026-06-14 14:43:50 +02:00
Marc-André Bombeck bf77f5243d Fix cycle ring width, tab strip and calendar tap targets on narrow phones
- Cycle ring: the fixed 120 px shrink-0 ring overflowed grid cells on
  360 px Android widths. It now caps at its target size but is free to
  shrink to fill its cell, so it never pushes the page wider than the
  viewport.
- Cycle tab strip: the four long German labels overflowed the equal-width
  flex-1 cells and forced a horizontal scroll at 375 px. Below sm the
  triggers size to their content (text-xs, no flex-1) and scroll cleanly;
  from sm up they reclaim the equal-width layout.
- Calendar day cells: bump the 40 px min-height to 44 px to clear the
  mobile tap-target floor.
- Add a Pixel-5 spec asserting no horizontal scroll on /insights and
  /cycle plus the calendar tap-target floor.

The cycle view also now feeds the profile's typical lengths into the
wheel so a low-data tracker gets the canonical four-phase ring.
2026-06-07 14:15:47 +02:00
Marc-André Bombeck 2b9d2370e9 fix(cycle): drop card log button, fill predicted-period days, assessment on batch
- Phase education card no longer carries its own log-today button (the
  top-level log entry covers it).
- Predicted-period calendar days render as a soft dashed fill instead of an
  underline, so they read as period rather than an unfinished dash.
- The derived batch route now attaches the deterministic per-score assessment
  for the five scores so the dashboard grid (and iOS) paints the explanation
  without a second per-id round-trip; guarded so it never fails the grid.
2026-06-06 20:36:29 +02:00
Marc-André Bombeck 546835a937 fix(cycle): trim duplicate phase card, drop redundant disclaimers, tidy settings
Remove the duplicate phase-education card from the calendar tab, keeping the
single instance under the wheel and surfacing its "you often notice this here"
chip row there. Drop the generic prediction-estimate caption on the predictions
tab for every goal except AVOID_PREGNANCY, where the contraceptive-safety caveat
stays as a deliberate medical note. Stop rendering the associations-not-causes
footer on the insights crosstab. In the cycle settings, align card-header
descriptions with their titles, and remove the standalone cycle export + purge
affordances now that cycle data rides the main export and account-level delete.
Move the web-push enable action into the card header row.
2026-06-06 20:10:15 +02:00
Marc-André Bombeck c1d8ff9db9 feat(cycle): surface a gated cycle summary card on the insights page
Add a compact cycle teaser to the main Insights overview for accounts with
cycle tracking enabled. The card shows the current phase, cycle day, and the
single headline phase finding, and deep-links into the full cycle insights tab
so the cycle tab stays the single source of truth.

The card mounts only when cycleTrackingEnabled resolves true from
/api/auth/me — the same gate the sidebar cycle entry uses — and renders nothing
for every other account. Phase and cycle day derive from the same calendar read
plus deriveWheelState the cycle wheel uses, and the headline reuses the shared
CyclePhaseHeadline, including its honest empty line when nothing has cleared the
significance check.

The cycle view now opens the insights tab when the URL carries ?tab=insights,
validated against the known tab values; CycleView is wrapped in a Suspense
boundary for the search-params read.
2026-06-06 19:44:01 +02:00
Marc-André Bombeck 32552f02ec feat(cycle): flow-shaded calendar, confirmed-ovulation marker, and cycle-length history chart 2026-06-06 17:01:33 +02:00
Marc-André Bombeck 81ec98fc97 feat(cycle): per-phase what's-happening education card 2026-06-06 16:45:15 +02:00
Marc-André BombeckandClaude Opus 4.8 eec3557137 feat(cycle): symptom-by-phase patterns on the insights tab
Add the cycle-native counterpart to the phase×vitals crosstab: for each
logged symptom, where in the cycle it clusters. A pure aggregator tallies
each symptom's occurrences across the phase-labelled days (the same phase
map the calendar uses), surfaces only symptoms logged on at least three
phased days, and reports per-phase counts + the dominant phase + its
share. The insights tab renders it as a proportional phase-hued bar with
the dominant phase named in words (never colour-only), observational and
never causal.

Also correct the phase-crosstab OpenAPI row enum, which had drifted
behind the runtime (mood + glucose channels were missing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:47:04 +02:00
Marc-André BombeckandClaude Opus 4.8 c21131e26e feat(cycle): second-audit feature, QoL, and wiring close
Close the second-round audit UI cluster:

- Cycle reminders: add the missing notification-event labels so the
  period-soon / period-confirm toggles read correctly on the
  notifications page, and point to them from cycle settings.
- Per-cycle history: render a recent-cycles list (range, length, period
  length, confirmed-ovulation chip) — the cycles were fetched but only
  the aggregate stats showed.
- Confirmed ovulation: carry it on the prediction DTO and surface it as a
  solid BBT-chart marker + a predictions row + a history chip.
- Log-day sheet: deselecting an enum now clears it (edits route through
  the nullable PATCH); period start/end work on any selected date for
  retroactive correction; "end period" shows only while one is open.
  Extract the payload builders as pure, unit-tested functions.
- Cycle-tracking enable toggle in account settings so any account can opt
  in before the gated page is reachable; wire the cycle data-purge button
  to the hard-delete route and document the route in the OpenAPI spec.
- Calendar legend: add the symptom dot, render fertile as a ring to match
  the grid; tighten the luteal prior bound to the engine clamp; mute the
  BBT ovulation-label colour for contrast.
- Share the cycle-start backward-walk between the wheel and the chart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:32:25 +02:00
Marc-André BombeckandClaude Opus 4.8 3375bbabac fix(cycle): second-audit safety, privacy, and write-path hardening
Address the second-round audit (server cluster):

- AVOID_PREGNANCY surfaces the fertile window, so it must carry the
  stronger "not a contraceptive method" disclaimer — wire the existing
  strong copy via a shared cycleDisclaimerKey(goal), server and client.
- Re-anchor the FOLLOWING neighbour cycle on a back-filled period start so
  an inserted-between-two-cycles start no longer leaves a stale open cycle.
- Resolve the sensitive-category encryption flag once per bulk/import
  batch instead of re-reading the profile per day; hoist the bulk cycle
  attribution in-memory (no per-entry query).
- Drop the raw driver message from the bulk wide-event meta (annotate meta
  is not run through the central redactor); keep the stable reason + code.
- Hard-purge now clears the cycle audit trail and the cycle reminder rows
  in the push-attempts ledger, so no dated reproductive trace survives.
- Discreet mode collapses the Web-Push coalescing tag and drops cycle
  metadata (phase) from APNs, matching the existing APNs event collapse.
- Add the reproductive intent field names to the central redactor denylist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:15:22 +02:00
Marc-André BombeckandClaude Opus 4.8 31a14ce7bf feat(cycle): premium cycle ring, BBT chart, and full read-state coverage
Finish the cycle web vertical's signature surfaces:

- Rebuild the cycle wheel to the wellness-ring premium signature — a
  clockwise clock-wipe that draws the phase arcs on from the 12-o'clock
  day-0 origin, a phase-tinted bloom on the active arc, the shared
  specular sheen, a day-number count-up, and a soft current-day marker
  halo. All on the wellness-tile surface, gated once per session and
  zeroed under prefers-reduced-motion.
- Add the basal-body-temperature chart to the predictions tab: the
  current-cycle BBT series with phase-tinted points, fertility signs in
  the tooltip, and an estimated-ovulation reference line (suppressed in
  raw-chart mode). Surface basalBodyTempC + ovulation-test + mucus on the
  calendar day DTO so the chart reads from the existing calendar load.
- Cover the read states the prediction/disclaimer surfaces were missing:
  pass the disclaimer to the predictions panel, render it on the calendar
  tab, add per-tab error + retry, and gate the settings spinner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:09:00 +02:00
Marc-André BombeckandClaude Opus 4.8 4b10bdc5da feat(cycle): single-day read endpoint, symptom severity, and a richer log-day sheet
Add `GET /api/cycle/day-logs?date=YYYY-MM-DD` returning the full day-log DTO
(or null) for one tz-anchored day so the web log-day sheet pre-fills instead
of opening blank — a blank re-log no longer nulls untouched or HealthKit-sourced
fields. Delete resolves the row id from the same read.

Persist an optional 1-4 Likert severity per symptom link (migration
0133_v1150_cycle_symptom_severity), carried through the shared symptom-link
writer and surfaced in the day-log DTO.

Sheet now captures the previously missing fields — spotting between periods,
pregnancy/progesterone home-test results, contraceptive kind — plus per-symptom
severity chips, a one-tap end-period boundary, and a Delete action. The wheel
ring marker sits at the 12-o'clock origin again (drop the double rotation), the
cycle view goes two-column on desktop, and the empty ring carries a first-period
call to action. New copy is localized across all six locales; the prediction and
fertility surfaces keep their disclaimers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:39:28 +02:00
Marc-André BombeckandClaude Opus 4.8 55f40df0ac refactor(cycle): wire the phase-correlation card into the insights tab
The cycle insights tab rendered an empty placeholder while the phase-contrast
card + its `/api/cycle/insights` feed sat unused. Wire them together: a
`useCycleInsights` read drives the headline finding + the luteal-vs-follicular
crosstab on the insights tab, matching the mood-factor crosstab styling. Drops
the placeholder card + a duplicate disclaimer component in favour of the ones
already in use, and removes the unused recency/phase-result/label exports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:26:13 +02:00
Marc-André BombeckandClaude Opus 4.8 7333d8e40f feat(cycle): add the cycle-tracking web vertical
Build the web UI for the cycle-tracking category against the existing
`/api/cycle/*` routes: a month calendar + circular phase wheel, a fast
log-day sheet, a predictions panel, phase-insight cards, and a single-page
settings form, all under a gated `/cycle` tab.

- Calendar + wheel: a hand-rolled SVG phase ring (0 KB runtime, matching the
  wellness-ring aesthetic with calm per-phase hues) showing the current
  phase + day-of-cycle, alongside a month grid where logged period days show
  a filled pip, the predicted next period renders as a ranged band (never a
  single date), the fertile window shows only when the goal allows it
  (the API suppresses it server-side otherwise), and ovulation + per-day
  symptoms carry markers. Reads `GET /api/cycle/calendar`.
- Log-day sheet: one-tap period start, flow / symptom / ovulation-test /
  mucus chips, basal-temperature, intercourse + protection, and a free-text
  note. Optimistic write to the day-log + period routes; invalidates the
  cycle query keys.
- Predictions panel: next-period range with a confidence pill, goal-gated
  fertile window, a still-learning state for fewer than three cycles or
  raw-chart mode, cycle-history stats, and the fixed non-medical disclaimer
  (reusing the drug-level-estimate disclaimer pattern).
- Insights cards render whatever phase-correlation cards the API surfaces,
  with a graceful empty-state until the feed lands.
- Settings: a single dynamic form where the inclusive goal selector drives
  the copy (conception framing only for trying-to-conceive), plus typical-
  length priors, raw-chart mode, sensitive-category encryption, discreet
  notifications, and export / delete. PATCHes `/api/auth/me/cycle-prefs`.
- Nav: a Cycle entry in the sidebar and the mobile More hub, gated on
  `cycleTrackingEnabled` from `/api/auth/me` and hidden otherwise.

Add the cycle query keys + dependent-key bundle to the factory, full copy
across all six locales, and dedicated per-theme phase hues. Plain
second-person voice, WCAG 2.1 AA, no new dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:06:20 +02:00