feat: build step 3 — /about/, minus its memberships; close Q40–Q43
Build and deploy / build-and-deploy (push) Failing after 6s
Build and deploy / build-and-deploy (push) Failing after 6s
Applies Pouya's rulings on Q42, Q41(a)(b)(c), Q43 and Q40, then builds `/about/` — six of docs/01's seven items. `/about/` ships WITHOUT a memberships group. R10 is a prohibition on shipping a page that lists memberships before they are re-confirmed; the re-confirmation is a fact only Pouya holds and was not obtained. The first version published all four and disclosed the gap in five places instead; both review agents rejected that. Q44 carries the question. Rulings: - Q42 — ENE, dispute-system design and pre-dispute technical advisory rowed; settlement counsel struck as a partisan role. The strike exposed a hole in the offering test, which now states the prior question it was missing. - Q41(a) — Q37 reaches prose, and prose is held to a higher bar. The sentence is now one constant, ASYMMETRY_LINE, because two hand-typed copies had already diverged inside one session. - Q41(b) — not restored; the implication turned out to be in three places, two of which survived the sweep that closed it. - Q41(c) — verified against the LAT's own Rules and extracted into docs/reference/lat-case-conference.md. Rule 2.4 makes "Pre-Hearing Conference" the Tribunal's own term for a case conference; the Rules contain zero occurrences of `mediat` in 66,593 characters. - Q43 — the timings are service commitments; PROCESS_FRAMING renders adjacent to them, not in a lede above. - Q40 — bundled to step 7 as R15, blocking cutover. Four review passes, 43 findings, nine of them defects in their own predecessors' fixes. The worst was mine: the false universal Q39 struck reached a public page. Also fixed a portrait ladder that upscaled 1.93x at 1024/DPR2 on BOTH pages — the shipped home page included — because its 960 ceiling was derived from the layout range where the image is narrowest. Verified: check/lint/build/audit clean; 0 upscaling across 11 device profiles; 0 overflow and 0 over-wide elements at 13 widths; 0 contrast failures across 127 and 88 painted pairs; 0 print failures against white paper; reveal 0 hidden under reduced-motion and print; zero JavaScript. Lighthouse NOT RUN — tool unavailable until step 7 (R11). HTML validator NOT RUN. Opens Q44 (memberships), Q45 (PDF bio), Q46 (offering-test gating; the glossary standard), Q47 (jobTitle without worksFor). Adds R15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148NztQskLKKApP5SzAA78e
This commit is contained in:
co-authored by
Claude Opus 5
parent
165d259f5c
commit
c576b9a85f
@@ -0,0 +1,97 @@
|
||||
---
|
||||
/**
|
||||
* The conversion band — maroon ground, one heading, one CTA. `docs/01` puts it
|
||||
* at the foot of `/` (item 8) and it belongs at the foot of every page that
|
||||
* wants an appointment enquiry.
|
||||
*
|
||||
* EXTRACTED 2026-08-28 ON `adversarial-reviewer`'S FINDING, and the finding was
|
||||
* not "this is duplicated" but "this is duplicated AND HAS ALREADY DRIFTED."
|
||||
* `/` and `/about/` carried identical markup and ~20 identical lines of CSS,
|
||||
* except `.contact-body` — `52ch` on `/`, `46ch` plus a `line-height` on
|
||||
* `/about/`. Two call sites, one already divergent, seventeen pages to come.
|
||||
*
|
||||
* NO PROPS AND NO SLOTS AT ALL, AND THAT IS A CORRECTION MADE ON REVIEW.
|
||||
* This shipped with `eyebrow?`, `cta?` and a named `heading` slot, all
|
||||
* defaulted, and **not one of the two call sites overrode any of them** — the
|
||||
* exact pattern this repo has already deleted twice with the reasons written
|
||||
* into the source: `Eyebrow.astro` (*"`tag?: 'p' | 'span'` had zero call sites,
|
||||
* so its `<span>` branch was unreachable code"*) and `SectionHeading` (*"two
|
||||
* mechanisms for one job… One way in."*). The header even argued against
|
||||
* `title`/`body` props and then added `eyebrow`/`cta`. Strings are inlined; add
|
||||
* a prop when a second call site actually needs one.
|
||||
*
|
||||
* The empty `Props` guard stays, though, and it is not decoration: without it
|
||||
* an Astro component's props widen to `any` and `<ContactBand class="x" />`
|
||||
* compiles clean while matching nothing — the parent-scope defect `CLAUDE.md`
|
||||
* records four times, and the one `Pill` was caught by. Verified by probe.
|
||||
*
|
||||
* THE `<h2>` IS FIXED AT LEVEL 2 rather than taken as a prop. Every page that
|
||||
* uses this band has an `<h1>` of its own and top-level sections at `<h2>`, so a
|
||||
* configurable level here is a way to skip a heading level by accident. If a
|
||||
* page ever needs otherwise, that is a spec question, not a prop.
|
||||
*
|
||||
* `CONTACT.responseTime` is rendered from the constant, never typed: §4 records
|
||||
* it as **a public commitment** that *"must read identically on `/contact/`, in
|
||||
* the inquirer confirmation email, and in any bio."*
|
||||
*/
|
||||
import Button from './Button.astro';
|
||||
import Eyebrow from './Eyebrow.astro';
|
||||
import { CONTACT } from '../data/site';
|
||||
|
||||
interface Props {
|
||||
children?: unknown;
|
||||
}
|
||||
const _props: Props = Astro.props;
|
||||
void _props;
|
||||
---
|
||||
|
||||
{
|
||||
/* NO BOOKING LINK, and that is not an omission: booking is parked
|
||||
(AGENTS.md R6) and `CONTACT.bookingUrl` is null, so `/contact/` ships the
|
||||
intake form with a reserved slot for an embed. Stated rather than silently
|
||||
dropped, on every page that renders this band. */
|
||||
}
|
||||
<section class="section section-accent contact-band">
|
||||
<div class="wrap contact-inner">
|
||||
<div class="contact-copy">
|
||||
<Eyebrow dot>Next step</Eyebrow>
|
||||
<h2 class="display contact-h">Start with a call.</h2>
|
||||
<p class="contact-body">
|
||||
Tell me the shape of the matter and who is involved, and I will tell you
|
||||
whether I am the right neutral for it. {CONTACT.responseTime}
|
||||
</p>
|
||||
</div>
|
||||
<div class="contact-action">
|
||||
<Button href="/contact/" variant="gold"
|
||||
>Request a consultation →</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.contact-inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
.contact-h {
|
||||
margin-block: var(--space-4) var(--space-4);
|
||||
font-size: var(--text-4xl);
|
||||
}
|
||||
.contact-body {
|
||||
/* 52ch, which is `/`'s value. The two call sites had drifted to 52ch and
|
||||
46ch; 52 is the one that shipped first and was reviewed. */
|
||||
max-inline-size: 52ch;
|
||||
line-height: var(--leading-body);
|
||||
}
|
||||
.contact-action {
|
||||
/* `0 1 auto` + `min-inline-size: 0`, NOT `flex: none`. `none` is `0 0 auto`,
|
||||
which refuses to shrink below max-content and pushed the band into
|
||||
overflow at 320px. This lets the button wrap instead. Measured on `/`. */
|
||||
flex: 0 1 auto;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -15,11 +15,53 @@
|
||||
* :global(), no wrapper div, and no rule that silently does nothing.
|
||||
*/
|
||||
/**
|
||||
* No props. A `class?: string` was declared and never passed, and a parent
|
||||
* cannot reach this root regardless — see SectionHeading for the measurement
|
||||
* and CLAUDE.md for the rule. The custom-property hooks below are how an
|
||||
* ancestor influences it.
|
||||
* NO PROPS — AND THE EMPTY `Props` INTERFACE IS LOAD-BEARING, NOT DECORATION.
|
||||
*
|
||||
* A `class?: string` was declared here once, was never passed, and a parent
|
||||
* cannot reach this root regardless — see SectionHeading for the measurement and
|
||||
* CLAUDE.md for the rule. It was deleted, and the deletion was written up as
|
||||
* "passing one is now a build error". **It was not.** With frontmatter
|
||||
* containing only comments, an Astro component's props widen to `any`, so
|
||||
* `<Pill class="chip">` compiled with **zero** errors, matched nothing, and let
|
||||
* the flex or grid child absorb the difference — silently.
|
||||
*
|
||||
* Measured by probe page, `<Eyebrow class>`, `<Pill class>`,
|
||||
* `<SectionHeading class>`, `<Button bogus>`: `astro check` reported **3 errors
|
||||
* — Eyebrow, SectionHeading, Button. Nothing for Pill.** Adding the three lines
|
||||
* below takes the same probe to **4 errors, 0 hints** (and no `ts(6196)`,
|
||||
* because the interface is referenced by the destructure below).
|
||||
*
|
||||
* CLAUDE.md names `Pill` as the next place the parent-scope defect will happen.
|
||||
* The guard that was documented as protecting it was absent on exactly it.
|
||||
*
|
||||
* The custom-property hooks in the style block are how an ancestor influences
|
||||
* this component: custom properties inherit, which is the one mechanism that
|
||||
* legitimately crosses the boundary.
|
||||
*/
|
||||
/**
|
||||
* `children` DECLARED, NOTHING ELSE. Getting to this line took two wrong turns
|
||||
* and both are worth recording, because each looked correct:
|
||||
*
|
||||
* - `interface Props {}` — rejected by eslint
|
||||
* (`@typescript-eslint/no-empty-object-type`), and it would have been the
|
||||
* wrong tool anyway: `{}` in TypeScript means "any non-nullish value", not
|
||||
* "no properties".
|
||||
* - `Record<string, never>` — passes eslint and does reject `class`, but it
|
||||
* also rejects `children`, so it broke the two REAL call sites
|
||||
* (`PracticeCard.astro:34` and `/about/`'s arc) while the probe page went
|
||||
* green on the thing it was testing. A fix that satisfies its own test and
|
||||
* breaks production is exactly what `/build` Phase 4 warns about.
|
||||
*
|
||||
* Slot content arrives as `children`, so `children` is the one permitted
|
||||
* property and every other prop is an error. Verified by probe: all six
|
||||
* components now reject `class`, and `<Pill>text</Pill>` compiles. Deleting
|
||||
* this re-disables checking at every call site.
|
||||
*/
|
||||
interface Props {
|
||||
children?: unknown;
|
||||
}
|
||||
const _props: Props = Astro.props;
|
||||
void _props;
|
||||
---
|
||||
|
||||
<span class="pill"><slot /></span>
|
||||
@@ -39,6 +81,23 @@
|
||||
line-height: 1.4;
|
||||
text-transform: uppercase;
|
||||
color: var(--pill-fg, var(--text-meta));
|
||||
white-space: nowrap;
|
||||
/* `nowrap` UNTIL 2026-08-28, AND IT WAS FINE UNTIL A PILL HAD FOUR WORDS.
|
||||
`/`'s six pills are one or two words (longest "Cross-cultural").
|
||||
`/about/` ships `Commenced August 2026`, and at a 200% DEFAULT FONT SIZE
|
||||
(root 32px — a real browser setting, not page zoom) that pill measured
|
||||
382.6px wide with its right edge at 430.6 in a 390px viewport:
|
||||
**41px of document overflow at 390, 111px at 320.** Injecting
|
||||
`white-space: normal` took 390 to **0** and 320 to **63**, 63 being the
|
||||
header residual docs/02 already accepts. WCAG 1.4.10 Reflow.
|
||||
|
||||
`normal` costs nothing at default size — a pill only wraps when it cannot
|
||||
fit, which is exactly when wrapping is the right answer.
|
||||
|
||||
WHAT IT LOOKS LIKE AT THE EXTREME, recorded so it is not later read as a
|
||||
new bug: at 320px with root at 32px, `Commenced August 2026` renders
|
||||
**224 x 119px** inside `border-radius: 999px` — a three-line stadium. It
|
||||
is ungainly and it is legible, in-viewport, and the alternative was
|
||||
111px of document overflow. */
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user