Retiring the standalone "Your calendar" step

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.

1 · Channels is 4 of 5 — the run is one screen shorter

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 DOMWho 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`).

Channels is 4 of 5 — the run is one screen shorter

2 · The retired screen renders NO progress bar

/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 DOMConnect 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 retired screen renders NO progress bar

3 · Teammates is 5 of 5 — a full bar on the last screen

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 DOMAdd 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`).

Teammates is 5 of 5 — a full bar on the last screen

4 · A legacy address still lands, query string and all

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 DOMWho books tours?. The old address resolves through to the Channels step — the redirect survived the page/gate split.

A legacy address still lands, query string and all

Captured anonymously, against stage

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.

PropFlow Docs