The leasing run is five screens, the retired screen renders no progress bar at all, and the offer stamp moved with it.
16 Sept 2026 · branch lane-c/retire-standalone-calendar-step · captured against propflow-stage · at a51b7f1629
Data note. Every name and balance below comes from propflow-stage, which is anonymized by construction (ADR-0097/0110 — phones in the impossible +1000 NPA, emails @example.test). The identical capture against prod would carry real tenants' names and balances and is not safe to attach anywhere, which is why the generator refuses to run against one.
The Channels step's I do
answer starts the admin's own calendar connect in one click, so a whole numbered step offering the same thing asked it twice. The leasing run is AppFolio → Sync → Review → Channels → Teammates now, and the bar says so: this screen is 4 of 5, where it read 4 of 6 before.
The number is not a literal anywhere — this screen, both of its shapes and its loading skeleton all call useWizardStep(WIZARD_STEP.channels), so a step leaving the run moves the bar by itself. That is the fix for the defect Fede walked into on 2026-09-10, when teammates sat on a hardcoded step={4} beside channels and the bar read full with a step still ahead of it.
Asserted in the captured DOM — Who books tours? · Continue. The Channels step renders with the tours section and its single Continue. The bar's count itself is NOT asserted here and cannot be: it lives in the progress element's `aria-label`, and this tool's assertions scan `innerText`. The picture shows the bar; the exact count is pinned by the jsdom suite, which reads that accessible name directly (`onboarding-run-branches-on-modules.test.tsx`).
/onboarding/calendar is still a live route — old consent links, bookmarks, and both legacy addresses land here, and Connect and Skip still work. It is simply in no run any more, so wizardStepNumber answers null, useWizardStep hands the header step: undefined, and the header renders no bar rather than a wrong one.
This is the frame that makes an old claim true. wizard-shape.ts has said since #8943 that a screen outside the run shows no bar; until this PR no real screen delivered it, because the two leasing-only screens still carried literals. The #8943 reviewer flagged exactly that as nit 1.
Asserted in the captured DOM — Connect your calendar · Clara books tours on your calendar. The screen renders in full — heading and body — which is what "still a live route" means. The absence is the point: no "Step N of M" string appears anywhere on it.
The last screen of a leasing run, and its bar is full. Before this PR it was 6 of 6 with the calendar step at 5; the screen itself did not change, only the run it reads from.
Its Back arrow now lands on Channels rather than the retired screen — also read off the run (wizardStepBefore), not repointed by hand.
Asserted in the captured DOM — Add your team · Finish. The last screen of the run renders its heading and its one ending. The bar's count itself is NOT asserted here and cannot be: it lives in the progress element's `aria-label`, and this tool's assertions scan `innerText`. The picture shows the bar; the exact count is pinned by the jsdom suite, which reads that accessible name directly (`onboarding-run-branches-on-modules.test.tsx`).
The company connect's old address is a redirect stub that carries its query string to /onboarding/channels, so a consent link minted before the fold still lands on a working screen with its outcome intact. This PR splits that target into a server page and a client gate; the redirect is untouched, and this frame is the proof it still arrives.
Asserted in the captured DOM — Who books tours?. The old address resolves through to the Channels step — the redirect survived the page/gate split.
Same route lane A established: a local dev server serves the wizard unauthenticated (src/middleware.ts short-circuits outside production, and the wizard layout runs its redirects only when a session exists), so these frames are the run as a customer meets it. The identities this tool signs in as cannot reach it — the smoke account is platform_admin, whom the layout redirects to /dashboard.
The run's own guard confirmed the data source was propflow-stage before a browser launched, and the contact scan ran over every captured page. A production frame is impossible by construction: scripts/ui-capture/target-guard.ts allowlists anonymized-by-construction sources and forbids widening that list in its own header.
What no frame can show is the hunk this PR most depends on: calendarWelcomeOfferedAt is now stamped by the Channels screen rendering, so an admin who answers My leasing team does
is not asked again on their next sign-in. A stamp is a write, not a pixel — it is pinned by channels/__tests__/offered-stamp.test.ts instead.