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
194 lines
10 KiB
Markdown
194 lines
10 KiB
Markdown
# 04 — Discoverability
|
||
|
||
The problem this project exists to fix. `AGENTS.md` §2 has the measurements.
|
||
|
||
> **Those measurements are under review — `AGENTS.md` Q34.** They were taken on
|
||
> 2026-08-25; a re-fetch on 2026-08-26 returned a bundler harness whose real
|
||
> `<head>` sits JSON-escaped inside a `<script>` and whose application lives in
|
||
> nine UUID-named files that were not fetched. Some of §2 reproduced exactly
|
||
> (the 2.2 MB single file, the placeholder `<title>`); some could not be
|
||
> reproduced from the served HTML at all. **Cite §2, and cite Q34 with it. Do
|
||
> not put any of these figures in public copy until Q34 closes.**
|
||
|
||
---
|
||
|
||
## The baseline being replaced
|
||
|
||
| | Now `[verified 2026-08-25]` | Target |
|
||
|---|---|---|
|
||
| Content in server HTML | `SML Company · DISPUTE RESOLUTION · Unpacking...` | Every word |
|
||
| Indexable pages | 1 | 17 + articles (19 fixed URLs, less the two `/legal/*` pages, which are `noindex` and excluded from the sitemap) |
|
||
| `<title>` | `SML Company · Dispute Resolution` — pre-rebrand placeholder | Unique per page |
|
||
| Meta description | none | Unique per page |
|
||
| `<meta viewport>` | **absent** | Present |
|
||
| Canonical URL | none | Every page |
|
||
| OG / Twitter tags | none | Every page |
|
||
| Structured data | none | Person, ProfessionalService, Article, FAQ, Breadcrumb |
|
||
| `robots.txt` | 403 | Served |
|
||
| Sitemap | none | Generated at build |
|
||
| Favicon | none | Full set |
|
||
|
||
Astro's static output solves most of this by existing. The rest is the spec below.
|
||
|
||
## Why static output matters more than usual here
|
||
|
||
Google can sometimes render client-side JavaScript. Bing largely does not.
|
||
LinkedIn's preview crawler does not. Slack's unfurler does not. **And the
|
||
crawlers behind AI assistants — increasingly how counsel and in-house teams
|
||
find a neutral — generally do not.**
|
||
|
||
A site that requires three CDN round trips and an in-browser Babel compile before
|
||
producing a sentence is invisible to all of them. That is the whole argument for
|
||
D1.
|
||
|
||
---
|
||
|
||
## Metadata
|
||
|
||
Every page passes through one `SEO` component. A page without it is not finished.
|
||
|
||
```
|
||
title 50–60 chars, unique. Pattern: "<Page> · Pouya Lajevardi"
|
||
Home: "Pouya Lajevardi · Mediation & Arbitration · Toronto"
|
||
ARTICLES ARE THE EXCEPTION: no " · Pouya Lajevardi" suffix.
|
||
The suffix is 18 chars, so a headline that already reads
|
||
50–60 renders at 68–78 — over this ceiling. Measured against
|
||
the five launch headlines in 03-content-spec.md, the suffix
|
||
rule fails 5 of 5; without it, 4 of 5 pass. An article's
|
||
headline IS its <title>; `seoTitle` in the frontmatter
|
||
overrides it when a headline that reads well is out of range.
|
||
src/content.config.ts enforces this and names the offending
|
||
string and its length in the build error.
|
||
description 140–160 chars, unique, written for a human, not stuffed
|
||
canonical absolute, https, trailing slash
|
||
og:title/description/image/url/type/site_name/locale (en_CA)
|
||
twitter:card summary_large_image
|
||
robots index,follow — except /legal/* which is noindex,follow
|
||
```
|
||
|
||
**OG images:** 1200 × 630. Never a screenshot.
|
||
|
||
**RULED 2026-08-27 (`AGENTS.md` Q40, R15) — TWO kinds of card, not one, and the
|
||
generator is deferred to build step 7.** This spec said "one template" for all
|
||
nineteen pages. Pouya split it:
|
||
|
||
> "A portrait is the **right** OG image for `/` and `/about/` — a face is the
|
||
> strongest social preview for a personal brand. It is the **wrong** one for
|
||
> nineteen pages, where a typed card carrying the page title would do the work.
|
||
>
|
||
> But do not build the generator now and do not leave 'portrait everywhere' as
|
||
> an untracked interim. **Ship it at step 7 alongside Insights, which needs
|
||
> per-article cards anyway — one build, one dependency, one review.**"
|
||
|
||
So:
|
||
|
||
| Pages | Card |
|
||
|---|---|
|
||
| `/` and `/about/` | The **portrait** crop, `src/assets/og-portrait.jpg`. Not an interim — the decided answer |
|
||
| Every other page | Generated at build with `satori` or `astro-og-canvas`, using the site's own type and palette: display headline on cream, infinity mark, designation line |
|
||
| Each article | Per-article card from the same generator — the reason the two jobs are one build |
|
||
|
||
**Until step 7 every page shares the portrait, and that is a RECORDED interim
|
||
that blocks cutover, not build step 3.** It is tracked as **R15** in
|
||
`AGENTS.md` §12 with its removal trigger, because a link preview nobody on the
|
||
team ever sees is exactly the kind of interim that becomes permanent by
|
||
never being raised. The dependency choice is made against R11 on the day, not
|
||
recalled from this paragraph.
|
||
|
||
## Structured data
|
||
|
||
JSON-LD only. Validate against Google's Rich Results Test before cutover.
|
||
|
||
| Type | Where | Notes |
|
||
|---|---|---|
|
||
| `Person` | `/about/`, referenced site-wide | `name`, `jobTitle`, `description`, `alumniOf` (Bond University), `knowsLanguage` (en, fa), `hasCredential` (Q.Med), `sameAs` (LinkedIn), `image`. **`jobTitle` = "Director of Firm Operations"; omit `worksFor`** — populating it either names the boutique (D16) or misstates the employer |
|
||
| `ProfessionalService` | Home | `areaServed` Toronto/Ontario, `serviceType` Mediation/Arbitration, `provider` → Person, `priceRange` once `/fees/` is real. **Never `LegalService`** — schema.org defines it as a business providing legal advice and *representation*, which asserts in machine-readable form exactly what D13 bars and §4 Forbidden calls out |
|
||
| `Service` | Each practice page | `serviceType`, `provider` → Person, `areaServed` |
|
||
| `Article` | Each article | `headline`, `description`, `datePublished`, `dateModified`, `author` → Person, `image` |
|
||
| `BreadcrumbList` | All nested pages | Matches visible breadcrumbs |
|
||
| `FAQPage` | `/for-parties/`, `/med-arb/` | Only where the visible page genuinely is Q&A. Never fabricate questions to farm a rich result |
|
||
|
||
**`hasCredential` must reflect reality.** Q.Med is held. Q.Arb is not. Marking an
|
||
unheld credential as held in structured data is a misrepresentation that happens
|
||
to be machine-readable.
|
||
|
||
## Crawlability
|
||
|
||
**`public/robots.txt` is the artefact — read it, do not read a copy of it
|
||
here.** This spec used to reproduce the file inline and the reproduction had
|
||
already drifted from it by 2026-08-26, which is the failure mode the `AGENTS.md`
|
||
§7 rule exists to stop.
|
||
|
||
**It disallows nothing, and that is deliberate.** This spec previously
|
||
prescribed `Disallow: /legal/` alongside `noindex` on those pages, and the two
|
||
cancel each other: a crawler forbidden to *fetch* a URL never reads the
|
||
`noindex` on it. `/legal/privacy/` and `/legal/terms/` are linked from the
|
||
footer of every page, so they are discovered regardless — and the likely result
|
||
of the pair was Google listing the bare URLs as "no information available", the
|
||
opposite of the intent, with the directive that would have suppressed them
|
||
sitting unread behind the wall. **`noindex` is what de-indexes; `Disallow` is
|
||
what prevents fetching.** Use the one that matches the problem, and never both
|
||
on the same path.
|
||
|
||
Do not block AI crawlers. Being read by an assistant that a general counsel is
|
||
using to shortlist neutrals is the point.
|
||
|
||
**Sitemap:** `@astrojs/sitemap`, excluding `/legal/*` and any `draft: true`
|
||
article. Submit to Google Search Console and Bing Webmaster Tools at cutover.
|
||
|
||
**Internal linking.** Every practice page links to `/mediation/` and
|
||
`/arbitration/`; those link back to the practice areas; every article links to
|
||
at least one practice page. This is what turns Insights into ranking power for
|
||
the pages that convert. Breadcrumbs on every nested page.
|
||
|
||
**404 page.** Real, styled, with search-intent links out. CloudFront must return
|
||
it with a genuine 404 status — not a 200, which the S3 website-endpoint pattern
|
||
gets wrong by default.
|
||
|
||
## Performance
|
||
|
||
Core Web Vitals are a ranking input, and the current build fails all of them.
|
||
|
||
| Metric | Budget |
|
||
|---|---|
|
||
| LCP | < 2.0 s, Slow 4G |
|
||
| CLS | < 0.05 |
|
||
| INP | < 150 ms |
|
||
| JS per route | < 100 KB |
|
||
| Lighthouse (mobile) | ≥ 95 all four categories — **not measurable until step 7, see below** |
|
||
|
||
> ⚠️ **Lighthouse verification is UNAVAILABLE until build step 7.** `@lhci/cli`
|
||
> was removed on 2026-08-26 — it was the sole source of all 10 `npm audit`
|
||
> findings (7 high), `0.15.1` is `latest` so there was no clean upgrade, and it
|
||
> could not run at all with no pages and no `lighthouserc`. The budget below is
|
||
> not suspended; the tool that measures it is absent. Re-add at step 7 under
|
||
> `AGENTS.md` R11, checking for a patched release rather than assuming `0.15.1`
|
||
> is still the ceiling. Until then, a run that skips this is skipping something
|
||
> known — not something forgotten. `AGENTS.md` §7 has the state.
|
||
|
||
How: static HTML, self-hosted preloaded subset fonts, AVIF/WebP with explicit
|
||
dimensions, critical CSS inlined, no third-party scripts on any page except the
|
||
booking embed on `/contact/` — and that one is lazy-loaded behind a click.
|
||
|
||
## Local and professional presence
|
||
|
||
Not code, but it belongs in the launch checklist: Google Business Profile for the
|
||
practice; ADRIC and ADRIO directory listings pointing at the site; a LinkedIn
|
||
profile whose headline and Featured section match the brand (brief §VIII);
|
||
consistent naming across all of them. **Not "name, address and phone"** — §4
|
||
publishes no phone number and no street address, only "Toronto, Ontario; by
|
||
appointment". Directory forms that demand a full NAP get what §4 verifies and
|
||
nothing more.
|
||
|
||
## Post-launch verification
|
||
|
||
- [ ] `curl -s https://adr.smlcompany.ca/ | grep -c "<h1"` returns ≥ 1
|
||
- [ ] Every page renders its full text with JavaScript disabled
|
||
- [ ] Rich Results Test passes on Person, ProfessionalService, Article
|
||
- [ ] OG preview renders correctly in LinkedIn Post Inspector and Slack
|
||
- [ ] Sitemap submitted to Google Search Console and Bing
|
||
- [ ] No page returns 200 for a URL that should 404
|
||
- [ ] Lighthouse ≥ 95 mobile on `/`, `/about/`, one practice page, one article
|
||
— **blocked until `@lhci/cli` is re-added at step 7.** Do not tick this box
|
||
from a manual Chrome DevTools run and call it the same check
|