Checks one running service: its API, failure handling, limits, and security. This page holds the cards for the ui context. The Service audit page lists every card. Report each check as PASS, FAIL, or N/A with file:line evidence. A FAIL carries the card's severity. Any blockFAIL means request changes. A should-fix or discussFAIL goes in the review but does not block.
Verdict: request changes - change, function - 2 block, 1 should-fix FAIL, 1 PASS, 14 N/A
FAIL [block] chg-expand-contract - src/a.py:42 - renames the column and drops the old one in one migration
FAIL [block] svc-sqli-authn - src/db.py:88 - proven: builds the WHERE clause with an f-string from request.args
FAIL [should-fix] chg-one-concern - src/b.py:7 - a logging rewrite rides along with the bug fix
PASS chg-breaking-change-taxonomy - src/c.py:15 - no signature or return shape changes
N/A: 14 cards, applies_when does not hold
Filters
Concerns Topics
A card appears under each concern or topic it lists, so a group view can repeat a card.
Checklist
CardSeverityConcernsRule
svc-async-content-reserves-spaceshould-fixusabilityperformanceReserve the space for late-loading content with set dimensions or an aspect ratio, so nothing already on screen moves.
svc-no-redundant-entryshould-fixusabilityaccessibilityDo not ask for information the user already gave in the same flow. Fill it in, or let the user pick it.
svc-reflow-and-zoomshould-fixaccessibilityusabilityMake the page usable at 320 CSS pixels wide and at 200% text zoom, with no sideways scrolling, no clipped content, and no blocked pinch-zoom.
svc-slow-operation-shows-progressshould-fixusabilityaccessibilityShow that a slow action is under way, and show its progress when it runs long.
Tool gate: what a linter or CI check could enforce instead of a reviewer.
Cards
svc-async-content-reserves-space
should-fixstableusabilityperformance
Applies when: A page loads images, embeds, fonts, ads, or fetched content after the first paint.
Rule. Reserve the space for late-loading content with set dimensions or an aspect ratio, so nothing already on screen moves.
Why. A page that jumps while someone reads or taps makes them hit the wrong target. A cumulative layout shift of 0.1 or less is good, and over 0.25 is poor, measured on real users at the 75th percentile.
FAIL
<img src="/hero.jpg">
<div id="promo"></div> <!-- script inserts a banner above the article -->
Counterpoint. "Ads vary in height." Reserve the common height, or the largest, and accept some blank space. A banner that pushes the article down is the worse trade.
Tool gate: Lighthouse CI assertion on cumulative-layout-shift.
Applies when: A multi-step flow, such as checkout or signup, asks for data in more than one step.
Rule. Do not ask for information the user already gave in the same flow. Fill it in, or let the user pick it.
Why. Typing the same address twice costs time and adds typos. People recognise what they gave more easily than they recall it, so show it and let them confirm it.
<!-- step 2 -->
<label><input type="checkbox" name="same" checked> Billing address is the same as shipping</label>
<input name="billing_address" value="12 Elm Road, Leeds" hidden> <!-- hidden while the box is checked; shown, pre-filled, when it is unchecked -->
Counterpoint. "Retyping a new password confirms it." That is a fair exception. So is data that is no longer valid, and re-entry that is the point of the task, such as a memory test. Browser autofill does not count: the page itself must supply the value.
Tool gate: none; a walk through the flow that lists each field asked twice.
Applies when: A page or app is served to browsers on screens of any size.
Rule. Make the page usable at 320 CSS pixels wide and at 200% text zoom, with no sideways scrolling, no clipped content, and no blocked pinch-zoom.
Why. Low-vision users enlarge the page instead of using a small screen. A fixed width or a clipped container hides content from exactly those users. user-scalable=no takes away their only tool.
.layout { max-width: 60rem; padding-inline: 1rem; }
.card { min-height: 7.5rem; }
.table-wrap { overflow-x: auto; } /* a data table scrolls in its own box */
Counterpoint. "Maps and data tables need two-way scrolling." They are exempt, but only the part that needs it. Wrap the table in its own scroll box, and keep the headings and controls around it reflowing.
Tool gate: axe meta-viewport; a rendered check at a 320 px viewport and at 200% text zoom.
Applies when: A page action, such as a save, an upload, a search, or a page load, can take more than about a second.
Rule. Show that a slow action is under way, and show its progress when it runs long.
Why. With no feedback, users click again, reload, or leave, and a double click can run the action twice. A bar tells them whether to wait. A message that says what is happening is better than "Loading". As a guide, show feedback after about one second. Past about ten seconds, show how much is done and offer Cancel where stopping is safe.
<button onClick={busy ? undefined : upload} aria-disabled={busy}>Upload</button>
{busy && <progress value={sent} max={total} aria-label="Uploading report.pdf" />}
{busy && <button onClick={cancel}>Cancel</button>}
<div role="status">{message}</div> // "Uploading report.pdf", then "Upload complete"
// aria-disabled keeps keyboard focus on the button; disabled would drop it
Counterpoint. "A spinner on a 200 ms call flickers." Right: below about a second, show nothing, or the placeholder just flashes. Skeleton screens suit page loads, and progress bars suit uploads and other long jobs.
Tool gate: none; throttle the network and run the action.
A draft card is not vetted. Report a FAIL of one under Draft findings, at should-fix at most, never in the ledger or the verdict. How a draft becomes stable
svc-page-title-per-route
should-fixdraftaccessibilityusability
Applies when: An app serves several pages or client-side routes under one HTML shell.
Rule. Give every page and every client-side view its own <title> that says what it is.
Why. The title labels the browser tab, the history, search results, and bookmarks, and it helps users find where they are. If every route says "My App", a user cannot tell the pages apart. In a single-page app the shell's title stays put unless the code changes it, so it goes on describing the first view.
FAIL
<title>My App</title>
<!-- the same title on /billing, /settings, and /billing/error -->
PASS
function usePageTitle(title) {
useEffect(() => { document.title = `${title} - My App`; }, [title]);
}
function Settings() { usePageTitle("Settings"); /* ... */ }
function Billing({ hasErrors }) {
usePageTitle(hasErrors ? "Error: Billing details" : "Billing details");
/* ... */
}
Counterpoint. "The router library sets it." Then test that it does for each route. A router that changes the URL and the view but not the title is the common failure.
Why it is a draft: the maintainer keeps it as a draft. Kept draft by Pablo's call; W3C, Mozilla and A11y Project back the Rule How a draft becomes stable
Tool gate: axe document-title catches a missing or empty title only; a test that navigates each route and asserts its document.title.