fix: resolve adversarial review round 2 — 9 findings, 8 of them in round 1's fixes
Build and deploy / build-and-deploy (push) Failing after 4s
Build and deploy / build-and-deploy (push) Failing after 4s
D19 caps the loop at two rounds, and this is what the second round is for. BLOCKING. Round 1 made NO_RETAINER_NOTICE a requireEnv and added it to no document, while the fix's own comment claimed docs/06 named it. The deployment list said five variables for a handler that needs six, so an operator following the cutover checklist would have deployed a function that throws at cold start on every invocation — 5xx from API Gateway, every inquiry lost from the moment /api/* was wired, loud in CloudWatch and silent to Pouya. docs/05 and docs/06 now name all six, and the comment that asserted the documentation existed is corrected rather than deleted. The intake route check added in round 1 could not fail: curl -w already prints 000 on a failed transfer, so `|| echo 000` double-appended and the failure arm was unreachable, and the pass arm accepted anything that was not literally 404 — including the 403 CloudFront returns when the /api/* behaviour is missing, which is the one distinction the check exists to draw. It now sends the correct Origin and asserts a positive: 303 to /contact/could-not-send/, which the handler returns before any DynamoDB write or email. Probed on refused/501/403/303; the old version passed the first three. Fixed in both deploy paths. Removing priceRange left three statements saying it was present or pending, one of them the stated reason /fees/ emits no Offer node. Deleting overtimeStartsAfterSessionHours left AGENTS.md §9 naming it and left Q59 recorded as open. The Google-as-processor fix was applied to the privacy policy's "Where it is stored" and not to "Who can see it", which still read "Nobody else has access". And the variable removal was justified with a path-scoped git grep — which also cannot see untracked files. The unscoped sweep found docs/06's variable table, the OIDC example, and .env.example still carrying them; .env.example also restates the execute-api hostname, falsifying a live claim in intake.ts that has been corrected. That file is not edited here: this environment denies read access to it, and nothing may edit a file it cannot read. It is in the batched list. Also: og:image:alt was the page title rather than the card's headline on 20 pages; og-card.ts documented the wrong path and invocation for the contact sheet; deploy-local.sh still said Q22's deploy credential "does NOT yet exist"; and the round-1 fix comments were trimmed per D19, though the ratio held at 0.44. Round 2 also confirmed the round-1 fixes by measurement: all 56 .btn instances across 22 pages, the consent checkbox's computed accessible name, the radio labels hit-tested at 44px, and og:proof exercised against synthetic article pages in a sandbox. Verified: check/build/check:claims/og:proof/check:intake/lint/bio:pdf all exit 0 on a clean build; 22 pages; Lighthouse 99-100 / 100 / 100 / 100, CLS 0.000. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
9f2d83c32f
commit
9f2d2eeb04
+16
-26
@@ -66,29 +66,21 @@ const classes = ['btn', `btn-${variant}`, className];
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
/* ⚠️ THE THREE HOOKS BELOW EXIST BECAUSE THIS BUTTON SHIPPED INVISIBLE.
|
||||
Found 2026-08-31 by `adversarial-reviewer` on `/fees/`, measured in headless
|
||||
Chrome against `dist/`: `{"t":"How an engagement runs →","color":"rgb(26, 22,
|
||||
20)","bg":"rgb(26, 22, 20)","ratio":1}`. `.btn-ghost` sets `color:
|
||||
var(--text)` — ink — and a border of `--border`, which is ink at 10% alpha.
|
||||
On a `.section-inverse` ground both are the background colour. **Ratio
|
||||
1.00:1: a navigation link the same colour as the panel it sits on**, worse
|
||||
than the gold-on-cream 2.10:1 this project treats as unshippable.
|
||||
/* ⚠️ THESE HOOKS ARE WHY `.btn-ghost` IS LEGIBLE ON A DARK BAND. Its own
|
||||
colours are ink text on an ink-at-10%-alpha border, which on
|
||||
`.section-inverse` and `.section-accent` are the background twice over —
|
||||
`/fees/` shipped this at a measured 1.00:1.
|
||||
|
||||
⚠️ AND LIGHTHOUSE SCORED THAT PAGE ACCESSIBILITY 100. axe's
|
||||
`color-contrast` rule SKIPS a foreground that exactly equals its background
|
||||
as "unable to determine" — so the a11y category cannot be the only contrast
|
||||
control here, and a computed-contrast sweep is not redundant with it.
|
||||
THEY ARE CUSTOM PROPERTIES AND MUST STAY THAT WAY. A parent cannot style a
|
||||
child component's root (CLAUDE.md), and a `global.css` descendant rule would
|
||||
tie at specificity (0,2,0) with `.btn-ghost[data-astro-cid]` here, so the
|
||||
winner would depend on injection order. Custom properties inherit, which is
|
||||
the one mechanism that crosses the boundary. `global.css` sets them; the
|
||||
fallbacks keep the on-cream appearance identical.
|
||||
|
||||
THE HOOKS ARE CUSTOM PROPERTIES, NOT A GLOBAL DESCENDANT RULE, and that is
|
||||
the load-bearing part. A parent cannot style a child component's root
|
||||
(CLAUDE.md), and `global.css`'s `.section-inverse .btn-ghost` would compile
|
||||
at specificity (0,2,0) — identical to `.btn-ghost[data-astro-cid]` here — so
|
||||
which one won would depend on injection order. `AGENTS.md` records that
|
||||
exact trap being hit once already, on `.btn-gold`. Custom properties
|
||||
INHERIT, which is the one mechanism that legitimately crosses the boundary;
|
||||
it is what `Pill` and `DefinitionGrid` already use. The fallbacks keep the
|
||||
on-cream appearance byte-identical. */
|
||||
Do not rely on the accessibility category to catch a regression here: axe
|
||||
SKIPS a foreground identical to its background as "unable to determine", and
|
||||
scored that page 100. AGENTS.md entry (ah) has the measurements. */
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
border-color: var(--btn-ghost-border, var(--border));
|
||||
@@ -99,11 +91,9 @@ const classes = ['btn', `btn-${variant}`, className];
|
||||
color: var(--btn-ghost-fg-hover, var(--accent));
|
||||
}
|
||||
|
||||
/* `background: var(--bg-inverse)` is ink, so on an inverse ground the pill has
|
||||
no boundary and reads as bare text — the milder half of the same finding.
|
||||
The label is gold-l at 11.09:1 on ink and stays legible, so this needs an
|
||||
EDGE rather than a new colour scheme: giving it a different ground would be
|
||||
redesigning a button shipped at step 5 rather than fixing a defect. */
|
||||
/* `background: var(--bg-inverse)` is ink, so on an inverse ground this pill has
|
||||
no boundary and reads as bare text. It needs an EDGE, not a new ground — the
|
||||
gold-l label already measures 11.09:1 on ink. */
|
||||
.btn-gold {
|
||||
background: var(--bg-inverse);
|
||||
border-color: var(--btn-gold-border, transparent);
|
||||
|
||||
@@ -150,19 +150,11 @@
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-meta);
|
||||
}
|
||||
/* ⚠️ NO TABLE RULES, AND THE THREE THAT WERE HERE ARE DELETED RATHER THAN
|
||||
FIXED. They set `display: block; overflow-x: auto` on the `<table>` itself,
|
||||
which has two defects: `display: block` **removes the table role** in
|
||||
Chromium and WebKit, so rows and cells lose their semantics for assistive
|
||||
technology; and an `overflow-x: auto` box with no `tabindex="0"` cannot be
|
||||
scrolled by keyboard (WCAG 2.1.1). The comment said *"the wrapper carries
|
||||
it"* — there was no wrapper; the properties were on the table.
|
||||
|
||||
Doing it properly means a real wrapper with `tabindex="0"`, `role="region"`
|
||||
and an accessible name, which in MDX means a rehype plugin or a `<Table>`
|
||||
component. **None of the five drafted articles contains a table**, and with
|
||||
all five `draft: true` no article page builds, so this CSS shipped nowhere:
|
||||
deleting it now and adding it with the first article that needs one is the
|
||||
same decision the `code` note above already takes for `<pre>`.
|
||||
Found by `adversarial-reviewer`, 2026-08-31, and correctly filed as latent. */
|
||||
/* ⚠️ NO TABLE RULES, DELIBERATELY. Do not re-add `display: block;
|
||||
overflow-x: auto` to the `<table>` itself: `display: block` removes the
|
||||
table role in Chromium and WebKit, and an `overflow-x` box with no
|
||||
`tabindex="0"` cannot be scrolled by keyboard (WCAG 2.1.1). A table needs a
|
||||
real wrapper with `tabindex="0"`, `role="region"` and a name — in MDX that
|
||||
means a rehype plugin or a `<Table>` component. No article uses one yet, so
|
||||
it arrives with the first that does, exactly as `<pre>` does above. */
|
||||
</style>
|
||||
|
||||
@@ -139,11 +139,24 @@ const ogImageUrl =
|
||||
)
|
||||
: new URL(ogCardPath(path), Astro.site);
|
||||
|
||||
// A typographic card's alt is its headline, which for every card in the
|
||||
// registry is the page's own <h1> — and `title` is the string already required
|
||||
// to be unique per page. The portrait keeps the person's name.
|
||||
/**
|
||||
* ⚠️ THE ALT IS THE CARD'S HEADLINE, AND IT WAS THE PAGE `<title>`.
|
||||
*
|
||||
* The comment here claimed *"a typographic card's alt is its headline"* while
|
||||
* the code fell back to `title`. Measured: `/fees/` emitted
|
||||
* `og:image:alt="Fees · Mediation and Arbitration Rates · Pouya Lajevardi"`
|
||||
* against a card reading *"Published in full, including what overruns cost."* —
|
||||
* an alt that did not describe the image, on 20 pages, and it would have
|
||||
* diverged further for the one article that sets `seoTitle`. Found by
|
||||
* `adversarial-reviewer` round 2.
|
||||
*
|
||||
* `OG_CARDS[path]?.headline` is the card's actual text for a registry page.
|
||||
* `title` remains the fallback for an article, where the card headline IS the
|
||||
* title, and `PORTRAIT.alt` for the two portrait pages.
|
||||
*/
|
||||
const resolvedImageAlt =
|
||||
imageAlt ?? (image || usesPortrait ? PORTRAIT.alt : title);
|
||||
imageAlt ??
|
||||
(image || usesPortrait ? PORTRAIT.alt : (OG_CARDS[path]?.headline ?? title));
|
||||
|
||||
// JSON.stringify does not escape `<`, so a "</script>" inside any string value
|
||||
// would close this element early and hand the rest of the payload to the HTML
|
||||
|
||||
Reference in New Issue
Block a user