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.
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.
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.
- Advanced cycle settings gains a "secondary fertility sign" selector
(cervical mucus default | cervix observation). It sits below the priors
in the advanced settings so it never forces itself on a casual user.
- The log-day sheet shows the cervix entry — position / firmness / opening
chips — only when the user picked cervix observation; otherwise the
cervical-mucus picker stays as before. Progressive disclosure: exactly
one secondary sign is offered at a time so the sheet never overwhelms.
- Web client types, the cyclePrefs patch, and the day-log payload builders
carry the new fields; copy added across all six locales.
A fever, illness, alcohol or a late/short-sleep reading can fake or mask
a temperature rise. Add a per-day `temperatureExcluded` flag on
CycleDayLog (migration 0156): when set, the engine drops the reading from
the cover-line baseline AND the shift evaluation, so a spike neither
fabricates nor masks a rise (Sensiplan: the cover line is drawn over the
last six unbracketed values).
Wired end to end — schema + Prisma, the engine's `detectTempShift`
filter, the Zod input/patch schemas, the day-log upsert (partial merge +
change detection), the read DTO, the calendar/insights/coach selects, the
backup round-trip, and the OpenAPI contract (a new boolean on the day-log
and calendar-day DTOs for the iOS client). The log-day sheet gains a
"mark reading as disturbed" toggle that appears once a temperature is
entered, translated across all six locales.
Citation: de.wikipedia "Symptothermale Methode"; myNFP "Temperaturkurve
auswerten" — Störfaktor bracketing.
When only a few cycle days are logged, the labelled forward run was short
enough that normalising it filled the whole ring with a single phase. For
a tracker below seven labelled days, derive the four-phase proportions
from the profile's typical lengths instead (period / luteal / cycle
length, textbook 28/5/14 defaults), so the dial shows the familiar
MENSTRUAL/FOLLICULAR/OVULATORY/LUTEAL arcs. The current phase and the
day-of-cycle marker stay driven by the observed labels.
The ring tile reads the shared, 5-min-cached cycle profile to source the
typical lengths (no extra request). Well-populated cycles keep the
observed-share spans unchanged.
The overview is a vertical stack on a single space-y-6 rhythm, but two
seams read wrong: Trends to period-in-review felt too wide while the
cycle summary to signals-of-the-day seam had no gap at all. The missing
gap traced to the cycle-summary card wrapping itself in a display:contents
box, which collapsed its margin so space-y skipped it.
- Give the cycle summary a real section wrapper (heading above the card,
matching every other section) so the inter-section margin applies.
- Document space-y-6 as the single gap, top to bottom; every section is
a real section or renders null, never a contents wrapper that strands
a phantom margin.
When cycle tracking is on, the cycle ring takes the Strain slot in the
health-scores strip rather than adding a sixth tile: the wellness strip
gains a hideStrain flag the insights page sets from the cycle gate, so
Strain is hidden for cycle accounts and shown unchanged for everyone else.
Fix the oversized vertical gap on the insights overview. The cycle summary
card set data-revealed on the same node that carried wellness-tile-rise,
but the entrance keyframe selector is a descendant combinator, so the
animation never matched and the card stayed at the rise rule's opacity: 0 —
invisible yet still occupying its box. Wrap the card so data-revealed sits
on an ancestor, restoring the entrance and removing the phantom gap.
Move the gated cycle entry directly after Medications (before Insights) in
the sidebar nav instead of appending it at the end, spliced by the
Medications index so the position survives a list reorder.
Add an operator-only password-reset CLI. It runs under plain node in the
production standalone image (which strips tsx) via the in-image pg and
@node-rs/argon2, reusing the app's Argon2id parameters through a shared
.mjs module so a reset hash matches one the app would mint. It matches a
single user by username or email, reads the new password without echoing,
never prints it back, and exits non-zero on zero or multiple matches.
Documented in docs/ops/password-reset.md.
Surface the cycle phase ring in the main Insights health-scores strip as a
gated sibling tile. It reuses the cycle wheel SVG, sized to a score-ring
tile, and sources day-of-cycle + phase from the same calendar read +
deriveWheelState the cycle page wheel uses, so the two surfaces never
disagree. Mounted only for a cycle-tracking account (the same /api/auth/me
gate the sidebar nav and the cycle summary card use), so its calendar read
never fires otherwise; it renders nothing while resolving, on error, and
when there is no active cycle — no gap, no placeholder.
Cap the adaptive cell size in the mood and medication compliance heatmaps.
A short window (a single medication, a 30-day mood calendar) left only a
few columns, so the container-width cell sizing ballooned and the square
grid blew the tile to roughly three times its neighbours' height. The cell
ceiling keeps the grid dense and left-aligned within the tile-height rhythm
at mobile and desktop.
- 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.
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.
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.
The phase-crosstab server emits display "mood" and "glucose", but the
client unit/label maps only covered the vital channels, so a surviving
mood or glucose phase correlation passed an undefined key to t() and
crashed the insights tab. Add the missing maps and harden both unit
lookups to fall back to an empty unit on any unmapped display.
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>
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>
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>
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>
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>