feat: build steps 7a-10 — the site is complete and reviewable at 22 pages
Steps 7a through 10 as one authorised run. Nothing deployed (D11).
7a Lighthouse returns as `lighthouse@13.4.1` + `chrome-launcher`, NOT
`@lhci/cli`. AGENTS.md §7's advisory attribution was wrong: the carriers
were @lhci/cli's own `tmp` and @puppeteer/browsers' `extract-zip`, not
Lighthouse, which audits clean. A deliberate deviation from R11's literal
trigger, recorded with what it costs. Local gate; CI has no Chrome.
7b OG card generator (satori + sharp) discharges R15 — 20 typed cards plus
per-article cards; the portrait stays on / and /about/ by Q40. Insights
plumbing: ArticleCard, Prose, the index, the article route, articleGraph,
and /'s section 7. Card copy is constrained structurally because text in a
JPEG cannot be grepped by check:claims: every headline IS its page's <h1>,
enforced by `npm run og:proof`.
7c Five drafted launch articles, draft: true / reviewedByPouya: false. An
independent compliance audit returned 76 findings and 57 unsourced
assertions; all blocking and should-fix applied.
8 /contact/, the intake form, and backend/intake/ (undeployed). Plain HTML
POST to a same-origin /api/intake with a 303 redirect, so the form works
with zero JavaScript. docs/05 records three deliberate deviations.
9 /fees/ on Q59's ruling — overtime runs from the session cap, and the
reservation point ships adjacent to the rate. One-page PDF bio discharges
R16; /bio/ is its source, so the circulated artefact stays inside the
review apparatus.
10 /legal/privacy/ and /legal/terms/, written to the backend as built. Three
of the policy's statements are derived and cannot drift.
Also: /about/'s inverse credentials band (approved at step 6); Q59 closed;
R15 and R16 discharged; and a fix to shipped copy — /practice/energy/ asserted
the absence of a regulation the source extract says must not be asserted.
Review: adversarial-reviewer, two rounds (D20/D19). Round 1 returned 16
findings including two blocking — an invisible ghost button on /fees/ at
1.00:1 that Lighthouse scored 100, and a privacy policy that named one data
processor when there are two. All 16 acted on.
Lighthouse, 22 pages, mobile: performance 99-100, accessibility 100,
best practices 100, SEO 100 on every indexable page, CLS 0.000.
AGENTS.md entry (ah) has the detail, including four of my own verification
commands that were wrong and what each of them nearly caused.
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
6cfe69033f
commit
210bc25a26
@@ -0,0 +1,98 @@
|
||||
---
|
||||
/**
|
||||
* `/contact/could-not-send/` — the failure half of the intake form's
|
||||
* POST-redirect-GET. Build step 8.
|
||||
*
|
||||
* WHY THIS PAGE EXISTS AT ALL. The site ships zero JavaScript, so a static page
|
||||
* cannot read `?error=` and render a message. The alternatives were: return an
|
||||
* error body from the API (the visitor lands on the API hostname with none of
|
||||
* the site around it), or say nothing (the visitor cannot tell whether the
|
||||
* inquiry arrived, on a form about a live dispute). A named page is the only one
|
||||
* of the three that leaves the reader knowing what happened.
|
||||
*
|
||||
* ⚠️ **IT DOES NOT LIST WHICH FIELD FAILED, AND THAT IS DELIBERATE ON TWO
|
||||
* COUNTS.** The handler deliberately does not return the error list — an
|
||||
* enumeration of the validation rules is a gift to whoever is probing them — and
|
||||
* the browser's own `required` / `type="email"` / `maxlength` handling has
|
||||
* already caught every case a person is likely to hit, inline and announced. A
|
||||
* server-side rejection means the submission was not made by that markup.
|
||||
*
|
||||
* ⚠️ **NO APOLOGY AND NO GUESS AT THE CAUSE.** "Something went wrong on our end"
|
||||
* is a claim about which end, and this page cannot know. It says what is true —
|
||||
* the inquiry was not recorded — and gives a route that does not depend on the
|
||||
* form working.
|
||||
*
|
||||
* `noindex`, and excluded from the sitemap in `astro.config.mjs`.
|
||||
*/
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import Button from '../../components/Button.astro';
|
||||
import Eyebrow from '../../components/Eyebrow.astro';
|
||||
import { getImage } from 'astro:assets';
|
||||
import ogDefault from '../../assets/og-portrait.jpg';
|
||||
import { pageGraph } from '../../data/schema';
|
||||
import { CONTACT } from '../../data/site';
|
||||
|
||||
const ldImage = await getImage({
|
||||
src: ogDefault,
|
||||
format: 'jpeg',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
});
|
||||
const graph = pageGraph(new URL(ldImage.src, Astro.site).href);
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Inquiry Not Sent · Contact · Pouya Lajevardi · Toronto"
|
||||
description="The inquiry was not recorded, so nothing has been received. Email the same details directly and they will be answered within two business days."
|
||||
jsonLd={graph}
|
||||
noindex
|
||||
>
|
||||
<section class="section hero">
|
||||
<div class="wrap">
|
||||
<Eyebrow dot>Not sent</Eyebrow>
|
||||
<h1 class="display hero-h">That inquiry was not recorded.</h1>
|
||||
<div class="prose">
|
||||
<p class="statement">
|
||||
Nothing has been received, so there is nothing waiting for a reply.
|
||||
</p>
|
||||
<p>
|
||||
The quickest route is email. Send the same details — your name, your
|
||||
role, the other parties, and a few sentences about the dispute — to <a
|
||||
href={`mailto:${CONTACT.email}`}>{CONTACT.email}</a
|
||||
>, and leave privileged detail out of it. {CONTACT.responseTime}
|
||||
</p>
|
||||
<p>
|
||||
Or go back to the form and send it again. If it fails a second time,
|
||||
email rather than trying a third.
|
||||
</p>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<Button href="/contact/">Back to the form</Button>
|
||||
<Button href={`mailto:${CONTACT.email}`} variant="ghost">
|
||||
Email instead →
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
padding-block: var(--space-9) var(--space-11);
|
||||
}
|
||||
.hero-h {
|
||||
margin-block: var(--space-4) var(--space-5);
|
||||
font-size: var(--text-5xl);
|
||||
}
|
||||
.statement {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--leading-body);
|
||||
color: var(--text);
|
||||
}
|
||||
.cta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3) var(--space-4);
|
||||
margin-block-start: var(--space-8);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
/**
|
||||
* `/contact/received/` — the GET half of the intake form's POST-redirect-GET.
|
||||
* Build step 8. `backend/intake/handler.mjs` sends a `303 See Other` here on
|
||||
* success.
|
||||
*
|
||||
* WHY A PAGE RATHER THAN A RESPONSE BODY. The site ships zero JavaScript, so the
|
||||
* form is a plain POST; without this redirect the visitor would be looking at
|
||||
* whatever the API returned, on the API's own hostname, with none of the site
|
||||
* around it. Landing on a GET also means a refresh cannot resubmit.
|
||||
*
|
||||
* `noindex` — it is a transactional page with no standalone value, and a search
|
||||
* result reading "your inquiry has been received" for someone who has not sent
|
||||
* one is worse than no result. It is excluded from the sitemap in
|
||||
* `astro.config.mjs` for the same reason.
|
||||
*
|
||||
* ⚠️ THE RESPONSE-TIME SENTENCE IS A PUBLIC COMMITMENT (§4, Q27) and must read
|
||||
* identically here, on `/contact/`, and in the confirmation email the handler
|
||||
* sends. Rendered from `CONTACT.responseTime`; never retyped, never softened.
|
||||
*
|
||||
* ⚠️ AND A BOT THAT TRIPS THE HONEYPOT IS SENT HERE TOO — deliberately, see the
|
||||
* handler. So this page must not say anything that is false for that case. It
|
||||
* says what was done, not what will happen to a specific record: "received"
|
||||
* covers a stored submission, and nothing here promises a reply to a submission
|
||||
* that was discarded.
|
||||
*/
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import Button from '../../components/Button.astro';
|
||||
import Eyebrow from '../../components/Eyebrow.astro';
|
||||
import { getImage } from 'astro:assets';
|
||||
import ogDefault from '../../assets/og-portrait.jpg';
|
||||
import { pageGraph } from '../../data/schema';
|
||||
import { CONTACT, NO_RETAINER_NOTICE } from '../../data/site';
|
||||
|
||||
const ldImage = await getImage({
|
||||
src: ogDefault,
|
||||
format: 'jpeg',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
});
|
||||
const graph = pageGraph(new URL(ldImage.src, Astro.site).href);
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="Inquiry Received · Contact · Pouya Lajevardi · Toronto"
|
||||
description="Your inquiry has been received. A confirmation goes to the address you gave, inquiries are answered within two business days, and nothing further is needed."
|
||||
jsonLd={graph}
|
||||
noindex
|
||||
>
|
||||
<section class="section hero">
|
||||
<div class="wrap">
|
||||
<Eyebrow dot>Received</Eyebrow>
|
||||
<h1 class="display hero-h">Your inquiry has been received.</h1>
|
||||
<div class="prose">
|
||||
<p class="statement">{CONTACT.responseTime}</p>
|
||||
{
|
||||
/* ⚠️ "A confirmation HAS BEEN SENT" WAS A STATEMENT OF FACT THAT TWO
|
||||
PATHS REACH THIS PAGE WITHOUT HAVING MADE TRUE, and this file's own
|
||||
header already said it must not be: *"this page must not say anything
|
||||
that is false for that case. It says what was done, not what will
|
||||
happen to a specific record."*
|
||||
|
||||
(a) The honeypot returns `redirect(SUCCESS)` before any write or any
|
||||
send — deliberately, because telling a bot it was detected is how the
|
||||
next bot stops filling the field. (b) The handler sends the two
|
||||
emails with `Promise.allSettled` and redirects here even if both
|
||||
reject, because the submission is already stored and a second attempt
|
||||
would duplicate the record.
|
||||
|
||||
So the receipt is stated as what happens rather than as what
|
||||
happened, and the clause after it is the route out either way. Found
|
||||
by `adversarial-reviewer`, 2026-08-31. */
|
||||
}
|
||||
<p>
|
||||
A confirmation goes to the email address you gave, repeating what you
|
||||
sent and linking to the privacy policy. If it has not arrived within a
|
||||
few minutes, check the address and email me directly at <a
|
||||
href={`mailto:${CONTACT.email}`}>{CONTACT.email}</a
|
||||
> — that reaches me whether or not the receipt did.
|
||||
</p>
|
||||
<p>{NO_RETAINER_NOTICE}</p>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<Button href="/process/" variant="ghost"
|
||||
>What happens next →</Button
|
||||
>
|
||||
<Button href="/fees/" variant="ghost">The rate card →</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
/* No ContactBand: the reader has just used the contact form, and inviting
|
||||
them to contact again is the one place that band would read as a defect. */
|
||||
.hero {
|
||||
padding-block: var(--space-9) var(--space-11);
|
||||
}
|
||||
.hero-h {
|
||||
margin-block: var(--space-4) var(--space-5);
|
||||
font-size: var(--text-5xl);
|
||||
}
|
||||
.statement {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--leading-body);
|
||||
color: var(--text);
|
||||
}
|
||||
.cta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3) var(--space-4);
|
||||
margin-block-start: var(--space-8);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user