Build log / archive
September 2026_
60 builds logged this month.
Signing out takes the notifications with it
- Logging out now ends the notifications too, on the web and the iPhone alike. Before, a browser or phone you signed out of kept receiving your pushes indefinitely — on a shared computer, the next person to sit down saw your Code Red alerts and cycle updates on their screen, and the bell even told them push was on. Signing out now detaches the device's push channel first, so what leaves with you is everything.
- "Sign out everywhere", changing your password and resetting it now do what their names promise for notifications as well: every device's push registration is revoked along with its session, so a lost or borrowed phone goes quiet instead of announcing your account from its lock screen forever. The device you did it from keeps its own notifications without missing a beat.
- Tapping a mood on the web dashboard used to quietly erase the same day's productivity and note if they'd been logged in the iPhone app — the tap rewrote the whole day with only what the web strip knew. A mood tap now touches moods and nothing else, so the morning's phone entry survives the afternoon's tap.
The quiet doors, closed
- The unsubscribe link at the bottom of every Mission Report email carried a token that — in the wrong hands — worked as a six-month key to the whole account: anyone who saw the link (a forwarded email, a corporate link scanner, someone with inbox access) could have read everything the account holds, including a partner's shared data. That door is closed: the link still unsubscribes with one click, exactly as before, but it can no longer be used as a sign-in. No action needed, and old links in old emails are covered too.
- Registering a Clip whose serial belonged to another account used to transfer it — and silently delete that account's entire temperature history — on nothing more than a made-up pairing code. Serials are short and readable by anyone within Bluetooth range, so this was a door anyone could push on. A Clip now stays with its account until its owner lets go: a new "Remove this Clip" on the Device page deletes its raw readings, keeps your detected heat exposures, and frees the serial for whoever holds the hardware next.
- The Sunday Mission Report — which can carry her cycle status and your heat record — no longer mails an address that hasn't been verified. An email typed with one wrong letter at signup is someone else's inbox; verify the address (the banner on the dashboard has the button) and the report resumes.
- Typing an email into the sign-in form used to answer differently for accounts that sign in with Apple — which quietly told anyone whether that address is on Kalcius. Sign-in now refuses every wrong attempt in the same words, and if your account is Apple-only, the helpful explanation goes where only you can read it: your own inbox, with the way back in.
- The "is today" reminder for a key date never actually arrived if you had a heads-up window set (everyone does — it defaults to a week): the advance nudge used up the one reminder the date was allowed. Anniversaries now get both — the heads-up, and the push on the morning itself. "1 days out" also finally reads "tomorrow", everywhere it appears.
- "Accounts linked" and "Account link ended" announcements now arrive every time they happen. Before, each was announced only once per account, ever — end a link and later re-link, and both of you got silence where the app promises those moments are always announced.
An invite can no longer answer "is this email on Kalcius?"
- Inviting a mate by email has always answered the same friendly "Invite sent" whatever actually happened — but the "Invites you've sent" list quietly told a different story: an address with a Kalcius account grew a waiting row with the person's display name on it, an address without one grew nothing. Typing any email into the invite box was a membership test. Now every invite grows exactly one waiting row showing only the address you typed — no name, nothing else — whether or not an account exists behind it, until the moment they accept. Declines and blocks leave the row standing too, so "still waiting" is all an inviter ever learns.
- The same rebuild made invites to mates who aren't on Kalcius yet first-class: the invite email now promises "create an account and the invite will be waiting in the app" — and it is. The moment they verify their address, your invite appears in their app as a real crew invite, instead of asking them to nudge you to send it again.
- Clicking "Not today" on a daily lever now ends the streak on the spot. Before, the board kept yesterday's count standing next to "Logged. Tomorrow's a fresh day." — a 7 that only became honest at midnight. An unlogged day still breaks nothing: the day isn't over until you say so or it is.
- Badges earned anywhere in the iPhone app now announce themselves the way they always have on the web: a toast slides in naming the badge, tap it to open the case. Before, only friend suggestions and date logs celebrated on the spot — a lever streak, a key date kept, a first draft or a first article read earned their badges in silence.
- The message composer now says up front when AI drafting isn't part of your plan, with the daily plan clearly staying free — instead of letting you write the whole thing in and refusing at the last click. (Nothing changes today: while billing is still being set up, the writer is on for everyone.)
Logging heat stops crying wolf, and three surfaces stop overstating
- Logging a heat exposure in the iPhone app — a sauna, a hot bath — saved the event and then showed an error anyway, every single time: the server answered the save in a rawer form than the app reads. Worse, the natural response to "couldn't save" is to tap Log again, which quietly created the same event twice and inflated the week's heat hours. Tagging a device-detected event's cause wore the same false error. The server now answers a heat log and a tag in exactly the shape the event list uses, so the save reads as the save it always was — once.
- Saving anything on the web Profile page — display name, height, weight units — silently made the page forget your account has a password: the password boxes under "Change email", "Sign out everywhere" and "Delete account" vanished, and submitting without them just bounced until you reloaded. The iPhone app's Settings had the same wobble after setting your height. A profile save now answers with exactly what the page loaded with, so the account keeps knowing what it knows.
- A brand-new account's first Sunday Mission Report opened on "60 clean days running" — sixty days of heat discipline claimed for an account created on Tuesday. The levers board learned to cap this streak at the account's own age back in August; the weekly email and the daily brief were still running their own uncapped copies. All three now state the same number from the same reading, so the app never congratulates you for days it never watched.
Confidence speaks the same sentence everywhere
- The Partner page's status panel used to end its prediction line with "confidence 62%" — a bare number that asks you to already know what 62 means. It now says what the iPhone app has always said in that spot, in the same words at the same thresholds: "Early estimate — very little cycle history to go on", "Firming up — a few more logged cycles will sharpen this", or "Based on several logged cycles". The "How these dates are worked out" explainer drops its percentage for the same sentence, so every surface that states the prediction's confidence — status panel, month grid, window explainer, and all four places on the phone — now describes the same number in the same plain English.
- A piece of armour rode along in the iPhone app: the month calendar's reading of that confidence figure could, under one server configuration, refuse the whole month and show a blank grid over a field it only needed for one small note. It now reads the figure the same tolerant way every other part of the app does — the worst a surprising value can do is silence the note, never the calendar.
The web calendar owns up to how much it knows
- Under the month grid in the iPhone app has always sat a small honesty note about the cycle predictions — "Early estimate — very little cycle history to go on", "Firming up — a few more logged cycles will sharpen this", or "Based on several logged cycles". The web Partner page's grid painted the same fertile windows and predicted periods with no such line: the shading implied a certainty the data may not have earned, especially in the first weeks when it's built on one or two logged periods. The web grid now carries the same note, in the same words at the same thresholds, fed by the same server answer — so the two calendars can never describe the same confidence differently.
- While tracking is paused the note stands down along with everything else, exactly as it does on the phone: the grid explains the pause instead of describing an estimate nobody is running.
The calendar's confidence note stops re-judging history
- Under the month grid in the iPhone app sits a small honesty note about the cycle predictions — "Based on several logged cycles", or, early on, "Early estimate — very little cycle history to go on". Paging back to an older month, that note was being re-computed as if the app were living in that month: it took the last period start it could see before the month's end as "her last period", decided she was weeks overdue, and collapsed to the early-estimate wording — under a tracker with months of clockwork history and a prediction it was confident about that same morning.
- The note now describes the estimate as it stands today, on every month you visit. If predictions are confident, the note says so under March just as it does under this month; if she's genuinely late today, the honest caution follows you back through history too, because "is she late" is a fact about today, not about the month on screen. It's the same number the Partner status shows, everywhere, by construction — the two can no longer drift apart depending on where you've scrolled.
The web calendar learns everything the phone's knows
- The month grid on the web Partner page showed a thinner picture than the iPhone app's: it painted only the current fertile window and the next predicted period, so paging back through her history meant bare cells where the phone showed each cycle's story. Both grids now draw from the same server answer. Every month shows its own fertile window with the predicted ovulation day marked, the PMS run-in, and the predicted period days — and opening a day names where in the cycle it sits ("Code GREEN · fertile window · day 12"), with an honest note whenever a claim is a projection rather than something logged.
- Wiring that up uncovered a bug the phone shared. Ask the calendar for a month that sits inside a long tracking break — the months of a pause, say — and the server rebuilt its picture from only the period starts it could see up to that month. It couldn't tell the stretch was a break, so it invented a cycle: a fertile week, an ovulation ring and a PMS run painted a few weeks into what might well be a pregnancy, and the quiet months beyond carried no explanation at all. The server now always knows about the next real period start beyond the month it's drawing, so a break stays quiet all the way through, every quiet day says why when you open it ("tracking gap — not read as a cycle"), and the days just before a logged period take their shape from that real date instead of a guess. Both apps get this fix at once, because both read the same answer.
- Two smaller courtesies rode along: every calendar cell now describes itself to a screen reader the way the iPhone app's cells always have — "Day 14, today, period predicted, fertile window" — instead of announcing a bare number; and if the month's shading can't be loaded, the grid says so and offers a retry instead of silently showing plain cells.
The front page stops calling a late week "day one"
- Two weeks ago the predictions learned to admit when they're wrong: a period that passes its predicted day unlogged reads as "late" — an ordinary state, honestly worded — instead of the forecast quietly rolling on. Today's sweep found the two surfaces that never heard. The first was the Today page's partner tile: seeing no countdown to report, it fell back to its day-one words — "Log the first day of her period to start predictions" — the setup prompt, shown over a tracker with real history, on exactly the day she's a week past the prediction. The tile now says what the Partner page says: how many days past the prediction, that late is ordinary, and to tread gently.
- The same wrong words covered the quieter state too. When no period start has been logged for weeks past the last prediction, forecasts pause rather than guess — the Partner page has said so plainly since that rule shipped, but the tile wore the same day-one setup line over it. It now says predictions are paused and why, with a door straight to the page where logging her next period starts them back up.
- And the month grid on the web Partner page was still painting "predicted period" in amber across days that had already come and gone unlogged — a prediction the status line above it had already owned up to missing ("was predicted ~15 Sept"). The iPhone app's grid stopped claiming those days the day the late state shipped; the web grid now stops too. A missed prediction stays a sentence the app owns, never a shape on the calendar.
The months of a pause stop being painted as a cycle
- Yesterday's fix taught the cycle estimate to read a long break as a break. One surface hadn't heard: the month grid in the iPhone app, which draws its days from a separate answer the server gives. Scroll back through a pause there and the app was still narrating it — the whole stretch counted out as one giant cycle, so a day last spring read "day 214, follicular/luteal", and the fortnight before the first period after the break carried a fertile-window bar, a predicted ovulation ring and a PMS shading. For a couple who paused because they were expecting, that is the app painting a fertile week across a pregnancy.
- The grid now stops at the same honest line the predictions do. The logged period at the start of the break keeps its days — that was a real period, logged. Everything after it, until tracking picks back up, claims nothing at all: no cycle-day count, no window, no ring, no shading. The cycle on the far side is an ordinary cycle again from its first day.
- Quiet without a reason reads as a bug, so those days say why. Tapping one opens the day with a line in the app's own words — "tracking gap — not read as a cycle" — and the plain explanation: nothing is predicted across a stretch this long, anything logged in it is kept, and it never skews her typical cycle length. Anything either of you did log during the break — a mood, a note, a night together — is still there, still editable, exactly where you left it.
A break in tracking is never mistaken for a cycle
- The pause shipped this week left one trap armed for the day tracking resumes. Cycle predictions are built from the gaps between logged period starts — and after a long break, the stretch from the last start before the pause to the first start after it looked like one enormous "cycle". A couple resuming after a pregnancy would have seen a 270-day gap treated as measured history: predictions could anchor to it, confidence would sit capped at 50% for the next half year of perfectly regular cycles, and the cycle-history panel would print "her recent cycles differ by up to 242 days" — arithmetic standing in the way of an ordinary restart, on exactly the timeline resuming is hardest.
- Now the estimate knows the difference. Any start-to-start stretch longer than the app's own honesty limit — the longest gap it would ever have narrated as one late-but-real cycle — is read as a break in tracking, not a measurement: it never enters the typical-cycle median, never counts as "her cycles vary", and never earns or costs confidence. The honest cycles on the far side of the break still count, so predictions restart from what was actually measured, or from the stated average until new history accrues.
- The cycle-history panel says all of this in plain words instead of quietly mislabelling: a long stretch is listed as "tracking gap — not read as a cycle", on the web and on the phone, on his page and on hers. The same rule already covered the smaller version — one unlogged month — which the median shrugged off; this closes the big one, the shape a resume actually leaves behind.
The pause finishes the job: every surface now tells the truth about it
- Yesterday's "we're expecting" pause stood everything down correctly — but two surfaces went quiet without saying why. The calendar on the web Partner page simply stopped painting the days ahead, which a fresh eye could read as a glitch rather than a choice. It now says, right in the month view, what the phone app already said: cycle tracking is paused, nothing ahead is predicted, and the history stays. Same words on both platforms.
- The deeper fix is in the daily suggestions. While tracking was paused, the writer behind them was told the cycle status was simply "unknown" — the pause leaking as ignorance. On the happiest state the app has, the one thing that day's suggestions should be warm about was invisible to them. The writer is now told the truth, with the same care the rest of the pause copy keeps: a couple who marked "we're expecting" get suggestions that can acknowledge the season — and are explicitly held back from speculating about the pregnancy, promising how it will go, or leaning on due dates and milestones. A plain "just pause for now" is told as exactly that, no reason attached, with the writer instructed not to go looking for one: those days get ordinary, kind suggestions about the two of you, nothing more.
The success path: "we're expecting"
- The one state the whole trying-for-a-baby posture points at finally has a place in the app. Until today, a couple who actually conceived watched the tracker drift into "her period is running late — tread gently", and then park forever on "log the first day of her next period" — copy that gets less true and less kind by the week. Now either of you can say it: a quiet door on the Partner page (and on her shared view, and on the phone) marks "We're expecting", and everything stands down at once — no more predicted dates, no fertile-window countdowns, no cycle nudges to either phone, no plans keyed to a cycle nobody is tracking. Nothing logged is touched, and the status says why it's quiet, warmly: congratulations, both of you.
- It isn't only for the good news. "Just pause for now" does the same standing-down with no reason asked — a break from tracking needs no justification, and the app never requests one. Turning tracking back on is one press, no questions asked, in either direction: whoever resumes may be doing so on the hardest week of their year, and the app's job then is to work, not to ask why. Predictions pick straight back up from the next logged period start.
- Like every change to the shared picture, a pause or resume is announced to the other side — categories, never content, and never negotiated. Her body makes it her call as much as his: both sides hold the same switch, on equal terms.
The iPhone app's privacy story, in the form Apple checks
- Apple requires every app submitted to the App Store to carry a machine-readable privacy manifest — a signed statement of what data the app collects and which sensitive system facilities it touches. The Kalcius iPhone app now ships one, written the way this product writes its privacy page: every line checked against the code, not asserted. It declares plainly that there is no tracking of any kind — no ads, no analytics, no third-party code at all — and names what the app does send to its own server to work: your email and name, your health and fertility data, the notes you type, your subscription status. Nothing else, and nothing for any purpose beyond making the product function.
- The declaration can't quietly rot, because the same automated checks that compile the app's logic on every change now hold the manifest to the code — in both directions. A future feature that starts touching a declared-worthy system facility fails the build until the manifest says so; a declaration the code no longer justifies fails it too, because claiming to collect what you don't is the other kind of dishonest. This clears in advance the wall that would otherwise appear on submission day: Apple rejects uploads whose manifest doesn't match the binary.
- The store listing's privacy answers — the 'nutrition label' shoppers see — now have one source of truth to be copied from, documented step by step for the day the app goes up.
The outage ends — and the watch learns to live within its means
- The build service started taking work again yesterday evening, and the self-healing built during the outage passed its first live test: the moment the first scheduled check actually ran, it restarted every check the outage had killed — and only those — without a hand touching anything. Both re-runs came back green, the alarm closed itself, and the board has been fully green since. Three days of outage, and the live site never went down once.
- Then the lesson was made structural, because this outage will otherwise return: the account's build allowance renews monthly, and the biggest single steady drain on it was the watchdog itself — the half-hourly-then-hourly checks alone consumed roughly a quarter of a month's allowance, billed a rounded-up minute at a time. The standing watch now runs on the project's own unmetered server, which probes whenever the cloud lane hasn't looked in forty minutes; the cloud lane keeps what only it can do — verifying every deploy from the outside within a minute, plus a few outside-vantage passes a day. Better coverage than the old schedule ever actually delivered, at a tenth of the cost.
- One honesty rule came with the move: the server checking itself cannot see a failure that exists only between it and the outside world, so a green seen from the inside no longer closes an alarm that was raised from the outside — it says what it sees, once, and leaves the alarm for the next outside pass to settle. Fourteen recorded scenarios pin the whole arrangement, including that one.
- The pre-deploy browser rehearsals stop re-downloading their browser from scratch on every run — it's now kept between runs and refreshed only when the pinned version moves. A minute and change of metered time saved on every push, which is the same lesson at smaller scale.
The watch tightens, and a false limit falls
- Day three of the build service refusing to start work — a test check dispatched at 16:06 died in four seconds without ever starting, the outage's unmistakable signature, and the account's compute allowance remains the one five-minute fix. The live site is unaffected and has been throughout: the server's own watch reports every page and the API healthy, the deployed builds current, and last night's backup fresh and verified.
- Today's watch had a hole in it: nearly seven hours passed between two of the server's fallback checks, because the scheduler that fires them sheds beats — the same weakness that took down the original watchdog, now visible on its replacement. The fallback now offers itself four times an hour instead of once. Each missed beat is a coin toss the schedule loses independently, so four chances an hour shrink the longest unwatched stretch in proportion; outside an outage every extra beat stands itself down in seconds, costing nothing.
- The better find: day one's bookkeeping claimed changes to the app's server couldn't ship while the outage lasts. Re-checking the machinery today showed that lane runs end to end on the project's own unmetered server — the constraint was never real, and two days of work were scoped around it. Server-side fixes and features are back on the table for as long as the meter stays stopped; only the web-page build, the browser rehearsals and the cloud watchdog still wait on it.
The alarm now says which kind of red it is
- Day two of the build service refusing to start work, verified again this morning: a test check dispatched at 08:06 died in five seconds without ever starting — the outage's unmistakable signature, and the account's compute allowance is still the one blocker. The live site remains unaffected: the server's own hourly watch reports every page and the API healthy, the deployed builds current, and last night's backup fresh and verified.
- One dishonesty remained on the alarm board, and today removed it. A check killed by the outage — dead before it ever started — was reported in the same words as a check that ran and truly failed, so the board read like a broken product when the only problem is a stopped meter. The watch now tells the two apart: a killed check is named as exactly that, with the one five-minute action that heals it written right on the alarm, while a check that genuinely failed keeps its plain red words and its full claim on attention. And when the watch can't tell which kind a red is, it refuses to guess soft — the failure stands in full.
- Rehearsed before shipping, the house way: fourteen recorded scenarios pin the words — a healthy board stays silent, a killed check names the fix, a true failure is never dressed up as the outage, and an unreadable answer never downgrades an alarm.
The board now picks itself up the moment the outage ends
- Day two of the build service refusing to start work — the account's compute allowance is still spent, and the five-minute fix remains where yesterday left it. The live site is unaffected and has been throughout: the server keeps the hourly watch from the fallback perch built yesterday, the API and every page answer, and the backups are fresh.
- What yesterday's fallback couldn't do is end the incident. Checks killed by the outage stay marked failed after the service comes back — nothing re-runs them until the next change happens to pass by, so the all-clear would wait on a person noticing. Now the first scheduled check that actually runs — which is itself the proof the service is back — restarts every check the outage killed, and only those: a check that ran and genuinely failed keeps its red and its claim on human attention, and nothing is restarted while another attempt is already underway.
- Rehearsed before shipping, the house way: eleven recorded scenarios prove a healthy board is left untouched, killed checks restart in order, a real failure is never papered over, and a refused restart is noted rather than allowed to break the watch itself.
The watchdog learned to change perches when its own perch fails
- This morning exposed a blind spot behind the blind spot: the build service that runs the watchdog stopped taking jobs (the account's monthly compute allowance ran out mid-morning), and with it went the half-hourly checks on the live site. For six hours nothing was watching — not because anything here broke, but because the machine hired to do the watching wouldn't start. The one machine that kept working was the server itself, which runs its own share of the build work unmetered.
- So the watchdog now has a second perch. Every time a scheduled check dies without ever starting — the unmistakable signature of the build service refusing work, distinct from a check that ran and found trouble — the server picks the probe up itself within the hour and runs the identical checks: the site answers, the API is healthy, the deployed build is current, the backups are fresh. The alarm lands on the same board either way, honestly labelled with its vantage point, because a server checking itself can't see a failure that only exists between it and the outside world.
- The alarm also learned some manners: when the same problem is still the same problem half an hour later, it no longer adds another identical message to the board. A long incident now reads as its turning points — what broke, what changed, when it recovered — instead of a wall of repeats burying the one update that matters.
Last night's new rules barred the app's own front door — found and fixed this morning
- Last night's security policy told your browser to allow connections to this site only. That was true in every rehearsal — where the app and its server share one address — but not in production, where the API lives on its own address. So from half past midnight until mid-morning, browsers obediently refused every call the web app made to its own server: signing in failed, and pages showed their couldn't-load lines. Your data was never at risk, the API itself was healthy throughout, and the iPhone app and the public pages were untouched — but the web app was effectively down, and the watchdog stayed green because it checks that the rules are served, not that the app can live under them.
- This morning's audit of last night's work found it and fixed it the durable way: every deploy now writes into the policy the one API address that very build actually calls, checks the result before anything ships, and refuses to ship a site whose rules would bar its own server. The two statements — where the app calls, what the policy permits — now come from the same single setting, so they can never disagree again.
- The watchdog learned the lesson too: every half hour it now checks that the live policy permits the API address the app is built against — the exact gap this slipped through. And the wrong assumption that caused it, written into two code comments claiming the app and API share an address, is corrected at the source so it can't mislead a future change the same way.
Your browser is now told the rules, and holds the site to them
- Until today the site's good behaviour — no third-party scripts, no trackers, no fonts fetched from anyone — was a fact the pre-deploy browser journeys verified, but nothing your browser itself enforced. Now every page ships a content security policy: the browser is told to refuse code from any origin but this site, refuse to be embedded inside anyone else's page (the classic clickjacking move), refuse forms that post elsewhere, and send no referrer when you follow a link away — a fertility site should not announce itself to the next site you visit. Alongside it: connections pin to TLS for a year, and the phone-permissions the app never uses (camera, microphone, location) are switched off site-wide, so a compromise can't quietly switch them on.
- The API now speaks for itself the same way. Every response is marked never-to-store: your readings, notes and cycle data must not sit in a shared machine's disk cache or an office proxy after you've signed out. That applies to every endpoint, with no exceptions to remember — a rule with exceptions is a rule that drifts.
- Proven the house way: the entire pre-deploy browser suite — every sign-up, every form, every chart, the partner link end to end — now runs under the enforced policy, so a rule that broke any page would fail the build before it could ship. The production watchdog checks every half hour that the headers are actually being served, and the privacy page now states all of it in plain words.
The new alarm shaken down: a dead email relay now sounds it too
- This morning's failure counter was audited instead of extended, the house habit for anything that ships promising to catch what nothing else catches. The promise mostly held — but the sweep-by-sweep check found one reminder job left off the list: the hourly sweep that sends the "your retest window is open" nudge could still die with nothing but a log line to show for it. It now counts its failures like every other background job, so the watchdog hears about it within the half hour.
- The bigger find was email. When the app sends a password reset, a verification link, a partner invite or the weekly report, a dead mail server has always been swallowed on purpose — those doors must not error at the person standing at them, because whether an email went out is nobody's business but the account holder's. Right so, but it meant the only trace of a dead relay was a log line on the server: resets and invites could silently stop arriving for days, which for a password reset means the account is unrecoverable the day it matters. Every failed send is now counted where the watchdog reads — recording only what kind of failure and when, never the address or the subject line, since a failure counter must not become a record of who was written to.
- Both paths are pinned by tests — including one that asserts a recorded email failure carries no recipient and no subject — and the whole suite, the production build and the boot check all came back green.
The API learned to notice its own failures
- Until today, the watchdog that checks the live site every half hour could only probe a fixed list of doors — the front page, the health endpoint, an article, the sign-in. An endpoint it didn't probe could start failing on every request and nothing outside a server log anyone rarely reads would say so; the app's worst outage happened exactly that way, and was caught by a person going looking four days in. Now the API counts its own failures: every unhandled server error, and every background job that dies mid-sweep — the reminder sweeps, the daily brief, push delivery — is recorded the moment it happens.
- The watchdog reads that count on every probe, so a broken endpoint anywhere in the app now raises the alarm within the half hour even if no probe ever visits it — the difference between a four-day silence and a same-morning fix.
- What gets recorded is shaped by the same rule as everything on the admin side: the route's shape, the kind of error and when — never who was signed in, never what they sent, never a real address that could carry a token. Failure visibility must not become a surveillance log, so it can't: the record has no field to put a person in.
The front door weighed, and held to it
- The public site — the front page, the Learn library, the privacy page, this changelog — got its first proper performance measurement, on emulated mobile hardware over a throttled connection, the way a first-time visitor on a phone actually meets it. The honest report: the text you came to read paints in about a tenth of a second from a local server, accessibility, best-practices and search-readiness score 100 across every page measured, and the only weight any public page carries is the framework runtime itself — no fonts fetched from anyone, no trackers, no third-party requests at all, which the browser journeys already enforce as a rule rather than a claim.
- Measured once is an anecdote; held to is a contract. Every deploy now weighs every public page — all of them, including each article and changelog month — against a stated budget with the current weight and headroom written down. The way page weight regresses is never a decision, it's an accident: a heavy import pulled in by a shared component, a dependency bringing friends. That accident now reads as a red build naming the assets that got heavier, instead of a quietly slower front door six months from now.
- The pre-deploy browser journeys got steadier too: the sign-out-in-one-tab-signs-out-the-other journey could fail on a loaded test machine — once because two different buttons honestly answer to "Log out" on the profile page, and once because the test aimed its click while the page was still taking its final shape. Both holes closed, and the journey then ran sixty times in a row under deliberate load without a wobble — a test that cries wolf teaches people to ignore the cry, and this one guards the door that ends your session everywhere.
A refusal now lands where you pressed
- This morning's cycle history panel was audited instead of extended, and the maths held: the lengths on screen, the median they land on and the confidence caps are pinned to the API's own estimator on all three platforms, and every angle tried — timezones, daylight saving, doubled dates, histories longer than the window — came back agreeing. The real find was in the furniture around it. When removing a period start is refused — the one designed refusal: the very first start in the history, while days after it hold logged content — the explanation rendered in a shared message area that could sit whole screens away from the button just pressed. The remove looked like it silently did nothing; the plain sentence explaining why was there all along, out of view. Now every remove door on every platform says the refusal inside its own confirm box — the web calendar's day panel, the new history panel, her shared page, and both iPhone screens — right where the tap just happened.
- A smaller find from the same walk: to a screen reader, the history panel was a column of identical "Remove this start" buttons — no way to tell which start each one erases. Every remove door now carries its start date in its accessible name, so what's about to be removed is named before it's pressed.
- The pre-deploy browser journeys got stricter with it: the refusal is now asserted inside the confirm box at both of his doors, so an explanation drifting out of reach of the button can never quietly ship again.
The cycle estimate shows its working
- This week every surface learned to add and remove period starts — and the doors kept promising that "the cycle lengths between them are what the estimate reads", without ever showing those lengths anywhere. Now there's a place to look. The Partner page (and a new Cycle history screen on the iPhone's Her tab) lists every logged start with the measured gap to the next one — "12 Aug → 9 Sept · 28 days" — marks which of those gaps the estimate actually reads (the most recent six; older history stays visible but is honestly labelled as not read), and states the result: the typical cycle length, named as the median of the gaps on screen. Until two starts exist there's nothing to measure, and the panel says exactly that — the number is still the stated average from setup, and switches to her measured cycle the moment it can.
- The same honesty extends to why confidence is what it is. When her recent cycles disagree with each other by four days or more, the panel says so and names the cap that puts on prediction confidence — the percentage on the status panel stops being an unexplained number. And on her side of the link, each shared cycle now shows its start-to-start length, with the same typical-cycle line above the list: the history is hers, and the predictions built on it shouldn't need his screen to understand.
- The history list is also a findable eraser at last. Removing a mis-tapped start used to mean knowing the date, paging the calendar to the right month and opening the day; her side of the link has always had a remove door on each cycle. Now his side lists every start on one screen with the same door, confirm step and re-tiling promise included.
- Proven the house way: the derivation is one shared rule pinned on all three platforms — the API's own estimator tests, a mirrored web suite and a mirrored iPhone suite all run the same numbers, so the panel can never quietly disagree with the prediction it explains — and the pre-deploy browser journeys now walk the panel too: backfill two months, watch both 28-day gaps and their median appear, remove the middle start from the list, and watch the maths honestly follow.
The eraser shaken down: a day's log now follows its date
- Yesterday's remove-a-start door was audited instead of extended, and the audit found a gap the new door had quietly widened. Adding or removing a period start redraws the cycle timeline — that's the point — but a calendar already open on another device (yours, or your partner's on her side of the link) still shows the old picture. Saving a day's flow or a note from that stale view could file it under a cycle that no longer covers the date — the log looked saved, but it was pinned to the wrong stretch of the timeline, exactly the kind of internal double-record yesterday's repairs were built to clean up. Now a day's log follows its date, full stop: wherever the save comes from, it lands in the cycle that actually covers that day on the current timeline, and in the rare case no cycle covers the day at all, the app says so plainly and asks for a reload instead of filing it somewhere wrong.
- The second find is insurance rather than a live bug: the rule that decides whether a day "records anything" — which the eraser consults before it will quietly delete an empty row — was a hand-maintained list of the day's fields. If a future change added a new kind of day log and forgot that list, a day holding only the new log could have been judged empty and dropped. That list is now checked against the database's own definition of a day on every test run, so forgetting becomes impossible: the mistake fails the build instead of deleting someone's data.
- Both are pinned by tests — the save that routes to the right cycle, the save that stays put, the plain refusal, and the field list that can no longer drift — and the whole stack was re-proven green, browser journeys against a real database included.
A mis-tapped period start can be taken back
- This morning's work gave every surface a door to log a period start on any day — and left an honest gap its own audit named: there was no door to remove one. A slip of the finger on the wrong calendar day was permanent, and worse than cosmetic — a start that never happened sits in the cycle history that predictions are computed from, quietly bending every forecast after it. Now the eraser exists everywhere the pen does: on the calendar, a day that holds a period start offers "remove it", with a confirm step that says exactly what will happen; on her side of the link, each cycle in the shared log has the same door, web and iPhone alike.
- Removing a start un-does it the way it was done: the timeline re-tiles as if it was never logged. The cycle before it re-extends over those days, everything logged on them — flow, moods, notes, all of it — survives untouched, and predictions re-anchor to the starts that remain. The one case that refuses is the very first start in the history while days are logged after it, because those days would have no cycle left to live in; the app says so in a plain sentence and leaves everything standing. And because removal takes something away from a history you both can see, the other side of a linked pair is told the shared calendar changed — same rule as every shared-log change: no silent edits between partners.
- Proven the house way: new tests pin the re-tiling both ways — the re-extended cycle, the re-opened current cycle, days that keep their logs, the doubled-day merge, the plain refusals — and a new pre-deploy browser journey walks the whole story against a real database: log history, log a day's flow, remove the start beside it, watch the flow survive, and watch the last defensible start refuse to go.
The new door shaken down: two flaws in its hinges, fixed same day
- This morning's backfill door was audited instead of extended, and the audit found two real flaws in the machinery underneath it. The first: for about a month, a period start logged a couple of days after the fact could leave the same calendar day recorded twice in the timeline's internals — an old row holding the flow she logged, a newer one holding a later edit. The new door's re-tiling would have tripped over such a day and failed with a blank error, on exactly the kind of history it was built to repair. It now recognises the doubled day, folds the two records into one — the flow, the note, everything logged survives; nothing is overwritten — and carries on.
- The second: two devices logging the same start date at the same moment — a double-tap, or both of you pressing your buttons together — could each pass the "is this date taken?" check before either had written, leaving two cycles starting on the same day: a zero-length cycle sitting in the middle of the maths that predictions read. The database itself now enforces one start per day, so whichever press lands second is told "a cycle already starts on that date" in so many words, no matter how the timing falls.
- Both fixes are pinned by tests — the doubled day that merges instead of failing, with every logged fact accounted for, and the race that loses politely — and the whole stack was re-proven green afterwards, same bar as any feature.
The history she already knows finally has a door
- Until today, every surface in Kalcius could only say one thing about a period: it started today. If it actually started while you were away for the weekend, there was no honest way to log it. And a brand-new profile was worse off still: predictions need her cycle history — the gaps between period starts are the whole estimate — yet the history she can recite from memory had no way in. A new profile stayed on "log the first day of her period to start predictions" until a period actually arrived, weeks of blank where there could have been a working forecast.
- Now a period start can be logged on any past day, through whichever door is nearest. Setting up the profile asks (optionally) when her last period started, so predictions stand from the first minute. The Partner page's "Period started today" button gains "…or on an earlier day" — a small panel that takes past dates in any order, last month's and the one before, each one sharpening the estimate. Any day on the calendar can be marked "her period started this day", matching the door the iPhone's calendar has always shown. And on her side of the link, the same earlier-day option sits beside "My period started today", on the web and in the app.
- The iPhone door is the honest confession here: the calendar has offered "Her period started this day" on past days since the app was restructured — and the server refused every date it was offered for. That promise is now kept. Under the hood, the timeline re-tiles itself around an inserted start (the cycle that covered that day ends the day before; anything logged on the days after follows them into the right cycle), duplicates and future dates are refused in plain sentences, and only a genuinely new period start counts toward the logging streak — backfilling history is remembering, not noticing.
- Proven the house way: new tests pin the timeline re-tiling, the refusals and the streak rule, and three new pre-deploy browser journeys walk the whole thing against a real database — the setup date producing a day-one prediction, months of history added out of order, and a calendar day becoming the day her period actually started.
The late rule shaken down: the calendar and the plan keep it too
- This morning's late-state work was audited instead of extended, and the audit found the new honesty stopping at the status panel's edge. While the status said "late — window withdrawn", the month calendar was still painting a predicted period over the very days that had visibly passed with nothing logged, and drawing a fertile window ten days out derived from that missed start — the exact speculation the status had just learned to refuse, on the surface built to be glanced at. The calendar now stops believing projections at a missed prediction: the days before it keep the windows the app honestly claimed at the time, and the days from it onward claim nothing until the next period start is logged. Its stated prediction confidence drops the same way the status's does.
- The daily plan had the same seam. Every Code Orange read as "PMS window" to the plan's register — including the late state, where that's simply the wrong fact. The register now hears what's actually happening — her period is running late against the prediction, nothing logged yet — with standing orders to tread gently, never speculate about why, and never let lateness, testing or timing anywhere near words meant for her. The suggested text itself doesn't change; what changed is that the reason underneath it is now true.
- Both fixes are pinned by tests: the calendar that keeps its pre-miss windows but claims nothing after the missed date, the paused grid past two weeks, and the plan register that never again calls late "PMS".
The predictions learn to admit when they're wrong
- The day after a predicted period passed with nothing logged, Kalcius used to quietly roll the forecast a whole month forward and declare "Steady state. Good window for plans" — green, on exactly the day its own arithmetic said something was off. Now it says what's true: the status turns Code Orange, names how many days past the prediction things are ("was predicted ~7 Sept"), and the copy stays calm — late is ordinary, predictions drift, tread gently. If you're trying for a baby, it adds the one thing worth knowing at that moment: this is when a test starts giving straight answers — her call, her moment, follow her lead. One discreet heads-up arrives per missed prediction, under the same lockscreen-privacy rules as every cycle notification.
- What the late state honestly withdraws: the fertile-window dates. A window projected off a prediction that just missed would be speculation stacked on speculation, so the window doors close until the next period start is logged — and after two weeks with nothing logged, predictions pause outright and say so, rather than steering plans off stale arithmetic. Logging the next first day picks everything straight back up.
- The estimate itself got more honest too. One unlogged month used to show up as a single double-length "cycle" that dragged the average — and every future prediction — for half a year; the maths now takes the typical of the recent cycles instead, which shrugs a missed month off the moment there's real history to outvote it. And the stated confidence now reflects how much her recent cycles agree with each other, not just how many are logged: five clockwork cycles and five wildly varying ones are no longer the same "90%".
- Proven the house way: the whole loop — a missed prediction turning the page honest — now runs in the pre-deploy browser suite against a real database, alongside new tests pinning the estimation maths, the late and paused states, and the one-nudge-per-missed-prediction rule.
The building blocks patched, and a watchman posted on them
- A sweep of the third-party components both halves of the product are built from found eight freshly published security advisories — one rated critical — in the library that sends Kalcius's emails (verification links, partner invites, the daily brief's plumbing), a file-upload component inside the API's web framework, and the toolchain that builds kalcius.com. Every one is patched as of today; the auditors on both sides now report zero known vulnerabilities of any severity.
- What was actually at stake, honestly: nothing here touched your data, and nothing was known to be exploited. The mail library's flaws were the ones that mattered most on paper — crafted addresses slipping past delivery checks — and are simply fixed. The upload component guards a door Kalcius has never opened (nothing in the product accepts file uploads), and the critical toolchain advisory affects a server kalcius.com deliberately doesn't run — the site ships as plain static files. Patched anyway: the cheapest day to close a hole is before anyone tries it.
- The find behind the find: these advisories had been published for anyone to read, and catching them still took a hand-run check. So the production watchdog — the same one that probes the live site every half hour — now asks the security registry about both dependency manifests on every probe. A new high or critical advisory against anything Kalcius is built from becomes an alarm on the build board within thirty minutes of publication, not whenever someone next looks.
- Nothing about the product's behaviour changed, and the whole stack was re-proven whole after the patching: every server and web test, the full 69-journey pre-deploy browser suite against a real database, and the deploy rehearsal — same bar as any feature.
The day's ships shaken down: the notifications keep the repair register too
- The morning's rough-patch feature was audited instead of extended, and the audit found the register rule stopping at the app's edge. The daily plan stands the trying-for-a-baby calendar nudge down while a rough patch stands — but the notifications hadn't heard: the daily brief would still open with "Fertile window opens in 2 days", and the standing window alert would still push "Make time for each other" mid-row. The exact pressure the rule exists to prevent, delivered by the layer that reaches you unasked. Both now consult the same flag the plan reads: the brief drops its countdown line while the flag stands, and the window alert waits.
- What still speaks, deliberately: Code Red and Code Orange — a warning to tread carefully aligns with repair, so only the nudge waits — and the brief itself, which now leads with the day's repair play like any other signal-grounded play. A patch stood down while the window is still open lets the alert speak once on the next sweep, as normal.
- The suppression is pinned by tests on both layers — the brief that refuses the countdown mid-row, the sweep that holds the window push and still sends Code Red, and the cleared or week-old flag that holds nothing back.
The plan learns about rough patches
- Your playbook has always had a shelf for what worked "last time a rough patch came round" — but nothing could tell the daily plan a rough patch was actually happening, so a proven fix never came back on the day it was needed. Now you can say so: one tap on the Comms page — "Rough patch at home? Log it" — and today's plan rebuilds in the repair register: ownership and small unprompted moves, with your own proven repair move back on the board, quoted in your words and carrying the rating you gave it.
- Repair replaces, it doesn't add. While the flag stands, the breezy just-because text and the trying-for-a-baby calendar nudges stand down — a cheery app mid-row is an app that wasn't listening. Every play still says why it's there.
- Standing it down closes the loop: "We're good now" asks one question — what helped? — and the answer lands in your playbook under "a rough patch", rated honestly, so next time isn't guesswork. And nothing about what happened is ever recorded: no notes, no causes, only the dates. The page says so, and so does the privacy notice.
- A forgotten flag can't haunt you — it lifts on its own after a week — and the iPhone speaks the whole loop from the plan sheet, which also stopped swallowing failures: a sent-mark or dismissal that doesn't land now says "Couldn't save that — you can retry" instead of doing nothing.
- The loop left machinery behind: the pre-deploy browser suite now logs a rough patch and watches the plan turn to repair with the proven move back, mends it into the playbook, and pins the log-that-dies-on-the-wire saying so — and both new states pass the accessibility gate like every other form.
The last two doors walked with fresh eyes
- The Learn library and the feedback box — the two dashboard pages this month's walks hadn't reached — were walked end to end, and with them every surface in the app has now had the treatment. The find that mattered most was in the reader: an article that failed to load and an article that doesn't exist said the same thing — "This article isn't available." A flaky connection could tell you a page of health guidance had vanished, with no way to find out otherwise. The two truths are now told apart: a genuinely missing article still says so plainly, and a failed load says it's a failed load, with Try again standing where the dead end was — on the web and the iPhone both.
- The library learned the same manners: a failed load of the article list used to strand a bare "Failed to load the library" with nothing to tap — it now carries the honest line and the way back, web and iPhone alike.
- The feedback box stopped quoting the browser: a report that died on the wire used to show the raw "Failed to fetch" — machine jargon in the one place built for plain words. It now says "Couldn't send that — you can retry", your words stay in the form, and the retry sends them. The iPhone speaks the same sentence.
- The walk left machinery behind: the pre-deploy browser suite now walks the in-app library, the reader's two truths told apart, and the feedback box's full send-and-retry loop — six new journeys, so none of it can quietly break again.
The crew and account pages walked with fresh eyes
- The Profile, Friends, Badges and Plus pages — your account and your crew — were walked end to end the way the couple's pages were this morning. The find that mattered most was on Profile: if the read of your account failed, the page said "Loading…" forever — no error, no way back, just a page that never arrives. Every one of the four now says plainly when a load fails, with Try again one tap away — the same manners the rest of the product learned this month.
- Friends stopped failing silently: removing a mate whose request didn't go through used to do nothing at all — no message, the card standing as if the click never landed; it now says "Couldn't remove him — you can retry." A failed action also used to leave a permanent error line over the page even after everything recovered; errors now clear the moment an action lands.
- The iPhone had the same diseases and got the same fixes: accepting, declining or blocking an invite, cancelling one, unblocking, marking a suggestion read and removing a friend all used to swallow failures whole — each now says so. The Plus page could sit on "Loading…" forever if the billing read failed (it now carries the honest line with Try again), and a weight-unit change that didn't reach your account looked saved — the phone now tells you the web won't match until a retry lands.
- Smaller: "In the crew since 12 Aug" now carries the year, matching every other date in the product, and the Plus page's failed load stopped telling you to refresh the page yourself — the retry is a button now.
- The walk left machinery behind: the pre-deploy browser suite now drives all four failure states for real — the profile that refuses to load silently, the badges case and billing page saying so, and a real two-account friendship whose failed remove speaks up before the retry lands it — so none of it can quietly break again.
The couple's pages walked with fresh eyes
- The Partner page — the cycle calendar, her status, the account link — was walked end to end the way My Fertility was this morning. The find that mattered most: if the read of her cycles didn't land, the page rendered anyway around the hole — a month of logged periods and moods presented as an empty calendar, the status panel silently gone. A doubt like that on the product's core surface is exactly the lie this month's walks exist to purge. The page now waits until everything has landed, a failed load says so plainly with Try again, and a failed refresh keeps your last good data standing under the honest line instead of blanking it.
- Her page got the same honesty: a dropped request on "Shared with you" used to strand a bare error with no way back — and only a definite "no link exists" answer from the server now shows the no-account-is-linked welcome, so a flaky connection can never claim the link is gone.
- The "Trying for a baby" toggle stopped failing silently: a tap that doesn't save now says "Couldn't save that — you can retry" instead of doing nothing at all, and every save on both pages — a day log, a period start, an invite, ending the link — speaks the same sentence when it doesn't land.
- Both pages now speak dates like a person: "Linked with Dee since 12 Aug 2026" instead of a raw machine date, the day editor titled "Log 8 Sept 2026", and her view's day table and cycle headings the same — no more ISO strings on the two most personal pages in the product.
- The walk left machinery behind: the pre-deploy browser suite now drives both failure states for real — the calendar that refuses to render around a hole, her page's honest line and recovery — and pins the silent-toggle fix, so none of it can quietly break again.
My Fertility walked with fresh eyes
- My Fertility — the test log, the trend charts, and the doctor's copy — was walked end to end the way the levers board and the planning pages were before it. The find that mattered most was on the doctor's copy: if the read of your medications and supplements failed, the sheet printed anyway — minus the ledger, with no hint anything was missing. That's the context a doctor reads your numbers in, on a document built for an appointment. The sheet now waits until everything has landed, and a failed load says so with Try again instead of printing an incomplete page.
- The same honesty arrived on the page itself. A failed read of your ledger used to vanish silently — taking with it the chart's context marks and, for anyone on TRT, the banner that reframes what low counts mean. A failed read of the retest clock just erased it. Any of those now says plainly that part of the page didn't load, with the way back one tap away. And deleting a test result that didn't go through used to do nothing at all — no message, row still standing; on the web and the iPhone both, it now says "Couldn't remove that — you can retry."
- The server also stopped accepting results that can't be real: a test dated in the future — one typo'd year — would have quietly frozen the retest clock and stretched every trend chart until the calendar caught up, and a motility of 420% would have flattened every honest point on its chart. Both are now refused at the door, and the web form caps its date picker at today the way the iPhone always has.
- The walk left machinery behind: the pre-deploy browser suite now drives all three failure states for real — the sheet refusing to print incomplete, the page's honest line, the delete that says so — and pins the server's refusals, so none of it can quietly break again.
The day's walks shaken down
- The day's two walks — the Clip's page and the planning pages — were re-read adversarially, the way every ship gets re-read here. The promises held, and two edges gave way under the re-read, both on Key Dates, both fixed.
- "Nothing tracked yet" now waits until the page actually knows that. For an account whose only dates had been and gone, a failed or still-arriving read of the full list could briefly claim nothing was tracked at all — the same class of fake empty state today's walks were purging. The claim now stands only once both reads have landed.
- The quick-add chips wait until the page knows what you track. This morning's fix stops "+ Christmas" minting duplicates, but that receipt depends on knowing what's already tracked — and in the moment before that answer arrives, or when it fails to, the chips were live without it. They now sit quiet until the page can honour the promise.
- Both edges are pinned in the pre-deploy browser suite, which walked all 52 of its journeys green against the real API before this shipped.
The planner's pages walked with fresh eyes
- Key Dates and Date Night — the two planning pages, shipped in the product's first weeks — were walked end to end the way the Clip's page was this morning. The find that mattered most on Key Dates: a one-off date that had passed simply vanished from the page. Still on your account, still in your export — just nothing anywhere to see it, re-date it or remove it with. Passed one-offs now stand in their own quiet "Past dates" section until you clear them or re-date them; the phone always showed them, and the web has caught up.
- The quick-add chips stopped minting duplicates: "+ Christmas" clicked twice used to track Christmas twice. A chip for a date you already track now shows as a receipt ("✓ Christmas") instead of a button.
- Both pages now speak dates like a person: the countdown cards say "25 Dec 2026 · every year", the date history says "7 Sept 2026" — no more raw machine dates — and the heads-up push about an approaching date now opens with "Fri 25 Dec" instead of an ISO string. The iPhone's date history also stopped shifting a logged night by a day for anyone west of Greenwich.
- And the failure manners the rest of the product learned this month arrived here: a failed load of your key dates says so with Try again (instead of a bare "Failed to load" over an emptied page), and a save or remove that doesn't land — a logged date night, a cleared key date, on web and iPhone — now says "Couldn't save that — you can retry" instead of looking done or silently doing nothing.
- The walk left machinery behind: the pre-deploy browser suite now quick-adds and countdowns key dates, keeps a passed one-off visible and removable, logs a date night and reads it back human-dated, and pins every one of those failure sentences so none of the lies can ship again.
The Clip's page walked with fresh eyes
- The Device page — the Clip's own home, and the heat-exposure log for everyone without hardware — was walked end to end the way the levers board and the writing desk were before it. The finds were all in how it behaves when something goes wrong. The worst: if the page failed to load your devices, it showed the "Have a Clip or bench rig?" register form — telling a Clip owner he has no device and inviting him to claim it again. A failed readings load claimed "No readings in this window yet", a failed heat-events load claimed "None yet — exactly how we like it", and a placement change that didn't save looked saved. Every one of those now says plainly that the load or save failed, with Try again one tap away — the same manners the rest of the product learned this month.
- The free-tier chart stopped smearing: picking 30 or 90 days on the free plan kept the axis at the full window while the data stopped at seven days, squashing your whole week into an unreadable sliver against the right edge. The chart now draws the week it keeps across the full width, and the amber note still says why it stops there.
- Smaller: heat events older than a week now name their date ("31 Aug, 14:05") instead of a bare weekday that could mean any of several days; the chart's last time label is no longer clipped mid-glyph at the edge; and a malformed readings request to the API is now refused politely instead of erroring.
- The walk left machinery behind: the pre-deploy browser suite now logs a heat exposure and deletes it for good, registers a bench rig through the form, sends a day of readings through the bridge's own door, watches the rules detect the hot run and tags it — and pins both failure states so neither lie can ship again.
The levers board walked with fresh eyes
- The levers board — the screen this product's honesty is built around — was walked end to end the way the writing desk was this morning, and it gave up real ones. The biggest: the weight history behind the Weight trend card failed to load for anyone who had actually logged a weigh-in — the one card state its own error line calls "a failed request, not an empty state", and no retry could fix it. Logging a weigh-in always saved (your trend chart and dashboard tile were never wrong); the card just couldn't read the history back. Fixed at the server, and the card now shows your latest reading and the trend line it always promised.
- Setting up for the first time also stopped fighting you: switching on your first lever used to make the rest of the catalog vanish mid-choice, so picking two or three meant hunting for a Configure button after every switch. The catalog now stays open until you say you're done — on the web and the iPhone both.
- And Cool running got honest about day one: a brand-new account switching it on was greeted with a 60-day streak — the counter's ceiling, presented as sixty days of lived discipline. The streak now starts where your account did.
- The web board also gained the phone's "streaks earn badges" pointer, and the walk left machinery behind: the pre-deploy browser suite now sets levers up as a first user, logs today and backfills yesterday, weighs in and reads the history back — so none of this can quietly break again.
The day's writing-desk work shaken down
- The morning's work was re-read adversarially, the way every ship gets re-read here, and it found the morning's own find one layer down. The behind-the-scenes panel had been made admin-only — but when the writer failed, the error message could still quote the AI service's raw reply and the server's own settings names to whoever was drafting. Now a regular account gets a plain sentence ("The AI writer couldn't answer just now — try again in a moment"), and the technical detail stays where it serves someone: with admins and in the server's own log.
- Smaller: the new "Couldn't save that" notice was raised beside both the daily plan and the drafts history whenever either had a failed tap. It now appears only beside the thing that actually didn't save.
The writing desk walked with fresh eyes
- The Comms page — the daily plan and the writer — was walked end to end the way the welcome flow and the session edges were before it, and it gave up real ones. The biggest: after drafting a message, a behind-the-scenes panel showed the writer's internal settings and instructions to whoever asked. It's an ops surface for tuning the writer, and it now shows only to admins; your drafts and your words were never anyone else's to see, and that hasn't changed.
- The small line beside Today's plan — the one that says why today's plays are rule-based — now tells the truth in every state. It used to point at an "allow AI processing" switch on servers where no such switch was on the page, and to say the writer needed switching on when it already was. Now it points only at doors that exist: what would change tomorrow's plan, from wherever you actually are.
- Smaller: the drafts history now dates itself the way the rest of the product speaks ("6 Sept 2026", not "2026-09-06"), and a tap that fails to save — a reaction logged, a play marked sent or dismissed — now says so plainly instead of looking like it worked.
- The walk left machinery behind: the pre-deploy browser suite gained a real writer endpoint of its own, so the AI surfaces — the consent gate, drafting, reactions, the plan's AI register — are now walked for real by the robot user before anything deploys, the same standing the partner link and the mail flows already had.
The day's session work shaken down
- The morning's session work was re-read adversarially, the way every ship gets re-read here, and it found real ones. The biggest: "signed out" was being declared too eagerly. If the app tried to renew your session at the exact moment the server was mid-deploy or briefly overloaded, it read the failed renewal as "your session is over" and threw the session away — on the web after this morning's work, and on the iPhone since the app existed, where even a signal dropout in a tunnel could do it. Now, on both platforms, only the server actually refusing the session ends it; a bad moment — yours or the server's — is just a failed load that a retry revives, still signed in.
- The sign-in page's return path — the "carry on where you left off" link — had its guard tightened. It already refused to follow addresses that led off the site; it now also refuses a class of lookalike paths that browsers quietly reinterpret as leading elsewhere. Nothing you'd ever type; exactly what someone crafting a malicious sign-in link would.
- Smaller: the very first load of the admin page on a fresh deployment could show half its panels as failed — the one-time promotion of the first admin racing itself across the page's parallel reads. It now settles in the admin's favour, and the pre-deploy browser suite runs against a fresh database every time, so it was flaky machinery as well as a wrong first impression. Both are steady now.
The first minutes get walked, and her briefing stops overpromising
- The welcome flow — the first minutes of every new account — was walked end to end with fresh eyes: every goal path, every form, at desktop and phone width. The good news: it all works. Everything the flow offers to save genuinely lands, and the first sitrep and the dashboard behind it report exactly what you logged.
- The real find was on the partner's side. When you accept a link invite, your first screen is a briefing on what the link means — and it still carried the promise from before sharing controls existed: "every note, nothing held back", whatever deal was actually struck. The invite email and the shared page have always named what's held back; the screen between them now does too. If everything is shared, it still says so plainly. If something is held back, it names the category on the spot — and if cycle tracking isn't shared, it no longer offers you a pen the link doesn't include.
- The walk left machinery behind: the pre-deploy browser suite now registers fresh accounts and walks the welcome flow's forms into a real first sitrep, and pins her briefing to the link's actual deal — so neither the first minutes nor that promise can quietly break again.
The session learns to end honestly
- Sessions can end away from the screen you're on: you sign out everywhere from another device, change your password, or a month passes. Until today the web app had no answer for that moment — every panel would show "Didn't load — try again" over a session no retry could revive, and you were left to work out that signing in again was the fix. Now the app notices the session has ended, takes you to the sign-in page, and says plainly what happened. The iPhone app already did the right thing here; the web has caught up.
- Your place survives the round trip: whatever page you were on — or any link into the app you follow while signed out — sign-in returns you exactly there afterwards, not to the front page. And signing out in one browser tab now signs out your other tabs too, instead of leaving them stranded mid-session.
- The sign-in and reset forms also got friendlier to password managers: they now say which field is which, so your manager fills the right one and offers to generate a fresh password where a new one is being set.
The bell joins the rehearsal
- The day's notification work was re-read line by line the way every ship gets re-read here, and then the pre-deploy browser suite — the robot user that walks the real app against the real server before anything deploys — learned to ring the bell. It now links a real couple, watches his bell count the news of her accepting, opens the panel, follows the row to the partner page it points at, and checks the count stands down once seen while the row itself stays on the record, dimmed. Then he holds back his notes, and the suite checks her bell carries the announcement — categories only, never content — and that her row leads to the shared page whose shape changed.
- One stitch tightened in the routing tests: the service worker keeps its own copy of the notification map (it can't import the app's), and the tests held the copy to the original in one direction only. They now hold it both ways, so a destination added to either side alone goes red before it ships.
The iPhone gets its notification inbox
- Until today, a notification on the iPhone lived and died as a push: if quiet hours held it, if you'd said no to notification permission, or if you swiped the banner away over breakfast, the news was simply gone. The web app has always kept the record — the bell in the corner, with its unread count. The phone now has the same: a bell on Today opens the inbox, every notification the product has sent you, unread ones counted on the badge and the quietest arrival kept until you've seen it.
- Each row in the inbox is a door, not a headline: tapping it takes you to the screen the news is about — Code Red to Her, the retest window to My Fertility, a crew invite to Friends — through the exact same routing a tapped push uses. Opening the inbox marks everything read, and reading in-app also stands down any push still waiting out your quiet hours: seen is seen.
- Behind the scenes, the notification list joined the shared test fixtures — real samples of what the server sends, generated by the serving code itself — so the web bell, the iPhone inbox and the server can never quietly drift apart on what a notification looks like.
A tapped notification lands where it points
- On the iPhone, tapping a push has taken you to the screen it's about — her status, the key date, the two-minute check — since mid-August. On the web and the installed app (which is the whole story on Android), every tap opened the dashboard front page and left you to find the news yourself. And if the app was already open, the tap stacked a second copy of it in a new window next to the first. Both fixed: a tap now lands on the page the notification is about — Code Red on the partner page, a key date on Key Dates, the retest window on My Fertility — and an open app window is brought forward and steered there instead of a new one piling up.
- The notification bell caught the same disease: its rows named the news but couldn't take you to it. Every row is now a link through the same map the push taps use — one map, pinned by tests on both sides so the web and the phone can never route the same notification to different places.
- The discreet-lockscreen setting is untouched by any of this: a redacted push still shows nothing but the product's name on the lockscreen. Where the tap leads was never part of what's visible — only where you land once it's your thumb on your phone.
The charts stop drifting west
- The day's two earlier rounds of fixes were re-checked line by line before anything new shipped — the email escaping proven inert again, the new date voices re-derived, the phone-width guard re-read. They held. But auditing the date work exposed an older cousin hiding inside the charts themselves.
- The trend charts — semen analysis history, the weight chart, the sleep chart — labelled their points, axis months and hover details under the viewer's own clock, while the dates they plot are calendar days. In the UK you'd never notice; for anyone west of Greenwich, every test date and week marker on those charts read one day early, and a point near a month boundary filed itself under the wrong month. The projected "you enter the zone around October" date could name the wrong month the same way. All of it — web and iPhone both — now renders through the same clock-proof date machinery as the rest of the app, so the day on the chart is the day you logged, from any timezone.
- The hover details on those charts also spoke machine ("03/09/2026") — they now say "3 Sept 2026" like everywhere else, and the notification bell's timestamps, which still varied their format with the browser's language setting, joined the house voice too ("3 Sept 2026, 14:22").
The post gets proofread
- Every email Kalcius sends — the password reset, the address confirmations, the partner invite with its exact statement of what is and isn't shared, the crew invite, the Sunday Mission Report — was rendered with real data and read end to end, the way an inbox would see it. The words held up; the plumbing needed work.
- The Mission Report's web version had a gap: the names you give your key dates were dropped into the email's HTML as-is, so a label containing web markup would have rendered as live markup — including a working link — inside your own report. Names are now treated as words everywhere, never as code, and a test keeps it that way. (The plain-text version, and every other email, was never affected.)
- The report's "this week" suggestion now always names the key date it's actually about. It picked the first date on the list rather than the imminent one — the list happened to arrive sorted so nobody ever saw it wrong, but "happened to" is not a guarantee we ship on.
- Two addresses were still pointing at the product's old domain from before the name change: the fallback sender address, and the crew invite's sign-up link was pinned to one domain rather than following the app's configured home. Both now follow the same single source of truth as every other link in every other email.
- The whole rendering layer moved into plainly testable code with its first test suite, and the mail server setup — including the signing that keeps Kalcius mail out of spam folders — is now written down properly for whoever runs the box.
The app earns its pocket fit
- There is no Android app — for half the likely audience the phone browser IS Kalcius. Today every page was walked at real phone size for the first time (the width of an ordinary iPhone), with an account carrying tests, weights, sleep, moods and a tracked cycle. The verdict is good news: nothing overflows, the calendar's day cells are comfortable tap targets, the menu drawer holds the whole map — the app was built responsive and it shows. The walk still caught what only eyes catch.
- The dates the app speaks are now human everywhere. The fertile-window tile on Today read "2026-08-31 through 2026-09-06" — machine dates on the most human of surfaces — and now says "31 Aug, through 6 Sept". The partner page's status line got the same voice, and every badge, feedback item and admin row that formatted its date as an ambiguous "03/09/2026" (month first? day first?) now writes "3 Sept 2026", unambiguous in any browser locale.
- Two phone-width squeezes fixed: the daily plan's cards sat their Copy and dismiss buttons beside the words, wrapping each suggestion into a ragged two-word-wide column — the buttons now drop below the words on a narrow screen and stay beside them on a wide one. And the three-month-lag drawing's two caption labels stack instead of colliding mid-phrase.
- The walk is now machinery, not a one-off: the pre-deploy browser suite gained a phone-sized pass that renders every public page and all fifteen app surfaces at that width and fails the build if any of them ever scrolls sideways — and proves the menu drawer actually opens, navigates and gets out of the way. A regression that makes the app wobble under a thumb now goes red before it ships.
The sharing deal is now written down in evidence
- The most privacy-critical answer the server gives is the shared log — what a linked partner sees of the days logged about her, under whatever sharing deal the link was set up with. That answer now lives in the day-old sample system as three real, committed server answers: one link sharing everything, one sharing only cycle tracking (mood observations and written notes withheld), and one sharing only observations (the cycle itself withheld). Each sample is generated by the server's own redaction code, so the withheld fields sit in the file as explicit blanks — and if that redaction ever weakened, the regenerated sample would show the leaked words in black and white in review, and every build board would go red before it could reach anyone's screen.
- The checks on all three sides read them: the server refuses to build if a sample stops matching what its code sends; the iPhone decodes each one with its shipping reader and asserts, day by day, that nothing withheld carries content; the website drives its "what is shared / what is withheld" announcements from the same files, so the categories her page declares can never drift from what the server actually holds back.
- Two more everyday answers joined the sample set while the door was open: the levers board (your daily commitments, streaks and check-ins — the panel that gained a bug fix on the phone only yesterday) and the sleep chart with its 7-hour band and trend. Twenty samples now stand where fifteen did this morning, each one a real answer, each one read by every platform's checks on every change.
The server's answers become the test paper
- This morning's new iPhone test rig had one honest asterisk: the sample server answers it tested against were copied out by hand. Careful hands, but hands — if the server's real answers ever drifted from the transcription, the tests would keep passing against yesterday's truth. As of tonight the samples are generated by the server's own code: fifteen real answers — your account, her cycle status and calendar, test results, the retest window, the two-minute check, notification settings, heat events, feedback, badges, the weight trajectory — written down exactly as the wire carries them, quirks and all (decimal values that arrive as text, dates to the millisecond).
- Three locks hold the loop shut: the server refuses to build if a stored sample stops matching what its code actually sends; the iPhone build refuses if any sample stops decoding — or if a new sample appears with no test reading it; and the website's checks read the same files. A change on any side that would break another now goes red on the build board before it can reach a phone.
- Writing it down honestly caught a small fib in the old hand-copied version: it tested the iPhone against a heat-event source label the server cannot actually send. Nothing was broken — but that is precisely the class of quiet drift this machinery exists to end.
The iPhone's engine room meets a compiler
- Until today the iPhone app had one honest gap in its safety net: the machinery that checks every update could catch a typo in the code, but not a wrong assumption — whether the app still reads the server's answers correctly, whether the doctor's summary sheet still builds the same document as the website's, whether a reference value quietly drifted. All of that was checked by a careful human read, every day, by hand. As of today the core of the app — how it reads your data off the wire, the doctor's copy, the WHO reference points, the weight-unit conversions, the shared vocabularies — is compiled and put through sixty-one real tests by machine on every single change.
- The very first pass caught something real: if the server ever sent an impossible calendar date (a February 31st), the iPhone would have quietly rendered it as a different day — early March, with nothing to say it wasn't yours. It now refuses to show a day the server never sent, the same rule the server itself follows.
- The tests also pin the two platforms together, word for word: the reference values on a semen analysis, the wording of what's shared with a partner and what's withheld, the labels on notifications — the iPhone and the website can no longer drift apart on any of them without a build going red.
The welcome mat was never out
- Today's two truth-telling sweeps got the house treatment: read back line by line, adversarially, before anything new was built on them. The iPhone sweep held clean — every new call checked against the real API, every screen's states re-derived. The website's sweep held too, but auditing it turned up something older hiding underneath: the "New here? Run first briefing" pointer on the Today page — the one card meant to greet a brand-new account and point it at the three-minute setup — could never actually appear for the person it was written for. A quirk in how the page judged "nothing here yet" meant the only visitors who ever saw it were ones whose connection had partly failed. The welcome mat existed; it was just never put out. It now greets exactly who it should: every read landed, nothing logged anywhere, here's where to start.
- A browser test now registers a fresh account and checks the welcome card is standing, so the front door can't quietly lose its greeting again.
- And one manner the iPhone learned yesterday reached the website: on a dropped connection, a linked partner's Today page no longer quietly rearranges itself while a read is failed — the tiles stay put and say what happened, same rule on both platforms.
The iPhone stops lying about bad connections
- This morning's fix taught the website to admit when a request fails instead of dressing the failure up as an empty page. This afternoon the iPhone app got the same lesson, because it had the same disease everywhere: on a flaky connection Today could declare "All cool" over a heat log it never fetched, tell a man with months of results "Nothing logged yet — log your first test", and invite a linked partner to "set up partner tracking"; Key dates, the Playbook and the medications ledger sat on "Loading…" forever with no way back; the feedback page could quietly show your sent history as if it had never existed; and My Fertility's quieter panels — the retest window, the two-minute check, the Body charts — simply weren't there, indistinguishable from features that don't exist.
- Every screen now says what actually happened, in its place, with a Try again that re-fetches — the same honest line, word for word, as the website's. Today gets the one-line summary at the top for the panels that hide when empty, the Comms screen never claims the AI writer is off when it merely couldn't check, notification settings explain themselves instead of sitting greyed out, and a heat event you delete or re-tag that didn't save says so instead of quietly reappearing.
- The sweep also caught a proper bug hiding in the calendar: the day editor's "Your levers" section — the quick Met / Not chips for logging your daily habits from the grid — could never appear at all, because the code that fetched the lever board only ran once the section was already on screen. It loads properly now, on every day within the two-week logging window, and a lever tap that doesn't save tells you nothing was saved.
The whole shop stops lying about bad connections
- Yesterday's find in the staff room — panels that quietly vanished when a request failed — raised an obvious question: does the rest of the shop do that too? It did, all over. On a flaky connection the feedback page could show you a history with nothing in it, as if everything you'd ever sent had gone; the playbook could greet a page of logged moves with "Nothing logged yet"; the medications ledger could claim "Nothing logged" over real entries; the weight card could say "No weigh-ins yet" to someone with months of history. Worst of all, the Today page: a failed heat-exposure check declared "All cool", a failed test lookup said "Nothing logged yet — log your first test", and a failed status check told a linked account to "set up partner tracking". Every one of those is a confident statement about your data made by a page that hadn't managed to read it.
- All of it now tells the truth. When a section fails to load it says so, in its place, with a Try again that re-fetches just what's missing — no reload, no lost work. Today gets one honest line at the top for the panels that hide entirely when they're empty, so a thin-looking morning can never silently be a broken one. The Comms page no longer claims the AI writer is switched off when it simply couldn't check, and notification settings no longer sit on "Loading…" forever with no way back. Deleting or re-tagging a heat event that didn't save now says it didn't, instead of quietly putting the row back.
- And the browser tests have learned to break the network on purpose: they now cut individual connections mid-page, watch the honest message appear where the false "all clear" used to be, and click Try again to see the real data land. A page that goes back to bluffing fails the build.