Vendors: the three first-read failure states, one card shape

After evidence round 1 on #11699: the failed read wears the same Clara card as the refusals, and the ended session offers Sign In.

24 Sept 2026 · branch p11/vendors-phone-mirror-import-door · captured against propflow-stage · at 5c58edcd16+local

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 · A failed read: say so, offer Retry

The list read answered 500. Round 1 (#11699) rendered this as a red line with a bare Retry, a different visual language from the two refusal cards beside it. It is now the same Clara card, with a secondary Try again button, the same words the page's incomplete-list banner uses.

Asserted in the captured DOM — We couldn't load your vendors. · Try again in a moment. · Try again. Asserted in the DOM after the list read answered.

A failed read: say so, offer Retry

2 · A refused read: a refusal, not "try again"

The list read answered 403. Retrying cannot fix a refusal, so none is offered. The copy says who can fix it.

Asserted in the captured DOM — You don't have access to this list. · Ask an administrator to check your access to these vendors.. Asserted in the DOM after the list read answered.

A refused read: a refusal, not "try again"

3 · An ended session

The list read answered 401. The card now has a Sign In button that returns to /vendors (/login?from=%2Fvendors).

Asserted in the captured DOM — Your session has ended. · Sign in again to see your vendors. · Sign In. Asserted in the DOM after the list read answered.

An ended session

How these states were reachable, and what the pictures cannot show

The stage smoke account's real /api/vendors answers 200, and the capture tool refuses both a rewritten response (page.route) and a page with a 5xx on it. The three states therefore rendered on an uncommitted fixture route inside the worktree. That route mounts the real VendorsClient from #11705 in the real workspace layout, and a page-local fetch shim answers only the list read (/api/vendors?…) with 500, 403 or 401. Everything else reaches the dev server untouched. This is the same method as pr-8565. The route and the shim are not in any PR.

What this proves: the client renders each failure honestly, never as "no vendors". What it does not prove: when the server emits each code. middleware.ts answers 401 to an unauthenticated /api/ call, and the route tests pin the rest. The text on each state is also pinned by VendorsClient.load-failure.test.tsx.

PropFlow Docs