Vendors: a first read that did not answer

What /vendors shows when its first list read fails, is refused, or finds the session ended: never "no vendors".

24 Sept 2026 · branch p11/vendors-list-error-rowmenu · captured against propflow-stage · at 5a7a965558+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 and no vendors are on screen. Before #11699 this fell through to the "no vendors" empty state, a claim about a roster nobody read. Now it names the failure and offers Retry, which re-reads with the loading state on.

Asserted in the captured DOM — We couldn't load your vendors. · Retry. 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 Sign In button that returns to /vendors lands in #11705. On #11699's code the card carries the copy alone.

Asserted in the captured DOM — Your session has ended. · Sign in again to see your vendors.. 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 #11699 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