Three detail pages carry a Sync from AppFolio action, and no others: work order, tenant, vendor. This page has been re-shot from scratch against the app as it stands after three merges — #5702 (daily work-order enrichment sweep), #5703 (4×-daily vendor contact sweep) and #5720 (the vendor page's header button moved into the kebab). Every picture below is a fresh capture from a signed-in app, with the menu actually open. The first version of this page argued the three surfaces were inconsistent and two of them had nobody driving them. Both halves of that are now fixed — and one of the two sweeps has visibly run in these pictures while the other has not yet had its first tick.
| Page | What you click | What it calls | What drives it automatically | What the picture below reads |
|---|---|---|---|---|
| Work order /maintenance/[displayId] |
Kebab (⋯) → Sync from AppFolio | POST /api/work-orders/[displayId]/enrich |
Scheduled, dailycron(0 9 * * ? *) — stalest-first, budget-bounded
(#5702).
The work-order rows already refreshed every minute; this covers the extra detail the
button pulls (vendor on file, access notes, urgency). |
First tick pending Synced 2 months ago. Across the whole table, 0 work orders carry an enrichment stamped in the last 24 hours. Honest reading: the schedule exists, the sweep has not run. |
| Tenant /tenants/[id] |
Kebab (⋯) → Sync from AppFolio | POST /api/tenants/[id]/sync |
Running, daily 11:00 UTC, same sync for every active AppFolio-linked tenant, stalest first. Predates all three of today's PRs — this is the design the other two were made to match. |
Observed Synced about 15 hours ago. Of the first 25 linked tenants, 25 of 25 carry a snapshot, every one stamped inside the same three minutes at 11:00–11:02 UTC. |
| Vendor /vendors/[id] |
Kebab (⋯) → Sync from AppFolio (was a header button until #5720) |
POST /api/vendors/[id]/sync |
Running, 4×/daycron(30 1,7,13,19 * * ? *) — stalest-first, paced against AppFolio's rate
ceiling, never outranks a human-initiated sync
(#5703). The
hourly roster job still handles who exists; this handles what they are. |
Observed Synced about 12 hours ago. Of the first 25 linked vendors, 24 of 25 now carry a snapshot from the last 12 hours. In the first edition of this page that number was 1 of 25. |
What changed since the first edition. That page argued two things: the control was inconsistent (a kebab on two pages, a bare button on the third), and for work orders and vendors somebody had to click or nothing refreshed at all. #5720 settled the first. #5703 settled the second for vendors, and the pictures prove it. #5702 settles it for work orders on a schedule that has not yet fired — which is why one cell in the table above is amber and not green.
The header collapses every action into one kebab. Sync from AppFolio sits in its own group under Mark Complete. This is the same work order the first edition photographed, deliberately: it is the control case for whether anything has changed underneath.
POST /api/work-orders/[displayId]/enrich.
The caption beside the kebab reads Synced 2 months ago on a work order still marked
Dispatched · In progress — the record's enrichment is stamped 10 June. This is the state
#5702's daily 09:00 UTC
sweep exists to retire, photographed before its first run rather than after. Do not read
this picture as the sweep failing; read it as the sweep not having happened yet.The number behind the caption. Of 571 work orders in the table, 26 carry an AppFolio enrichment at all, and the freshest of those is 26 hours old — so zero were enriched in the last 24 hours. That is what "first tick pending" means, counted rather than assumed. The next honest version of this page is one taken after 09:00 UTC.
Same control, same wording, one item in the menu. The caption reads Synced about 15 hours ago — not because anyone clicked, but because the 11:00 UTC sweep had been through.
POST /api/tenants/[id]/sync.
The AppFolio Profile card below the fold is the payload that sync maintains — financials,
lease and occupancy, recurring charges, pets, vehicles, insurance. Names, email and phone are the
anonymised stand-ins the stage table uses: the @example.test address and the
(000) area code can never reach a person.Not one lucky example. Of the first 25 AppFolio-linked tenants, 25 have a synced profile on file, and every stamp falls between 11:00:48 and 11:02:23 UTC — a single sweep walking the list, not 25 people clicking.
This is the picture worth looking at. The labelled Sync from AppFolio button that used to sit in this header is gone; there is a kebab there now, and it is open in the shot. Beside it, Synced about 12 hours ago. The AppFolio Profile card below — which read Not synced yet in the first edition of this page, on this same vendor — is filled in.
POST /api/vendors/[id]/sync.
Two merges are visible in this one frame:
#5720 in the kebab that
did not exist here this morning, and
#5703 in both the
12-hour caption and the populated AppFolio Profile card. That timestamp is the sweep's own first
run at 13:30 UTC — nobody clicked anything to produce it.The before-number, and the after-number. The first edition of this page counted the first 25 AppFolio-linked vendors and found 24 had never been synced at all, the one exception stamped 3 June by a human. The same count today: 24 of 25 carry a snapshot from the last 12 hours. The one that does not is a stage-only fixture with no real AppFolio counterpart to pull.
The old header button had a disabled state and a tooltip that said "Match this vendor to AppFolio before syncing." A kebab has no room for that, so #5720 traded it for something you can read without hovering: no menu at all, and the words Not linked to AppFolio where the timestamp would be.
Why no empty menu. A kebab that opens onto nothing is worse than no kebab, and the route would refuse the call anyway. 21 of the 839 vendors in the table are in this state — mostly in-house crews and locally-created vendors that have no AppFolio record to be matched to.
Captured with the repo's own capture and safety modules
(scripts/ui-capture/* — the same ones scripts/ui-evidence.ts uses), which
refuse to run against a data source that is not anonymised and scan every rendered page for reachable
contacts before anything is published.
origin/main including
#5720
(2f67aa5bd0),
#5702
(1fe4170c39) and
#5703
(f963b1ebcc). Photographed from a worktree whose src/ tree is
byte-identical to origin/main — verified with git diff origin/main -- src,
which was empty.propflow-stage — the continuously-mirrored, anonymised copy of production, read back
from the running server's own /api/health rather than from a label. Production itself
was never written to and never screenshotted.…-wo-enrichment-schedule · cron(0 9 * * ? *) and
…-vendor-contact-schedule · cron(30 1,7,13,19 * * ? *).verifyRecordContacts, the same belt the stage replicator runs before writing a row.What this page still does not settle. It shows where the control lives, what covers it automatically, and — for two of the three — that the cover has actually run. The work-order sweep is verified scheduled and not yet verified run; its first tick is 09:00 UTC on 13 August, and its run rows will land on the admin jobs surface like every other sync job. Nothing here was clicked: the sync routes write to AppFolio, and no button on a real customer entity was pressed to make these pictures. The counts quoted throughout are from the stage mirror, so they track production's shape rather than being production's own numbers.