fix: regenerate favicon.ico with a transparent ground; tick Pouya's read-through
public/favicon.ico shipped with no transparency: all three frames declared a 32-bit alpha channel and then carried alpha=255 on every one of their 256/1024/ 2304 pixels, ground opaque cream rgba(250,247,242,255). Pouya's read-through finding, confirmed by parsing the ICO container directly. The render source carries true alpha (2,272,386 transparent px, 20,795 partial), so this is an export, not a mask derived from the cream ground — R13's harder branch did not fire and R13 is unchanged on its own terms. New scripts/icons.mjs + npm run icons re-derives the icon from the committed master: asserts the source is still the documented crop (R14), verifies a candidate file and renames on success so a rejected build cannot replace a good favicon, and runs a boundary-colour halo test. Composition is unchanged — ink bbox and pixel count identical at all three sizes. apple-touch-icon.png is byte-identical and stays opaque cream deliberately; the reason lives in docs/reference/brand-assets.md §The icon set, with the bar and a pointer in BaseLayout.astro, docs/06 and R13. docs/06: the read-through is ticked, and the cutover callout drops to ONE blocker — Q60's waiting period. Two adversarial review rounds, 15 findings, all resolved, none declined; stopped at two per D19. claims-auditor correctly deferred to cutover per D20. Gates on the committed bytes, exit status read: check 0, build 0 (23 pages), check:claims 0, check:intake 0, og:proof 0, lint 0, lighthouse 0 (worst of 23 99/100/100/100). Nothing deployed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
4735989f0b
commit
67847d94fa
@@ -932,7 +932,7 @@ never being raised again.
|
||||
| R10 | ✅ **FIRED AND SATISFIED 2026-09-02 — THE CUTOVER EVENT AND THE CONFIRMATION, IN THAT ORDER. THE ROW STAYS LIVE.** Asked of Pouya on 2026-09-02 as a one-line question — *are ADRIC, ADRIO, the three OBA sections and the CTF all still current?* — and **answered the same day: all current.** It was asked rather than looked up, which is the entire content of this reminder: **a stamp is not a renewal receipt, and re-reading an old one is not re-confirming.** It was open for part of the day, alongside R18 which fired and was satisfied the same day. **Re-stamped on all three surfaces, because `memberOf` is emitted and the constant feeds both:** §4's memberships row, `CREDENTIALS.memberships` in `src/data/site.ts` and the R10 note in `src/data/schema.ts`. ⚠️ **AND THERE ARE TWO ARRAYS, NOT ONE — re-stamping is not the same act as checking they still agree.** `CREDENTIALS.memberships` feeds `/about/`'s visible list and `/bio/`; **`MEMBERSHIP_ORGS` feeds `/process/` §Confidentiality and the `memberOf` triples**, and `site.ts` records that the two differ on three of four lines. `_MembershipParity` compares their **`['length']` only**, so a substitution passes `npm run check` in silence. An earlier form of this row said one constant fed both surfaces, which would have left `/process/` and the JSON-LD publishing a lapsed membership after a correct-looking edit — the OCNI failure with a green build (`adversarial-reviewer`, round 1). `docs/06`'s item is ticked and **re-armed for the next republish** — the trigger is an event and events recur, which is why this row does not close on being satisfied. Original text follows. ⚠️ **A THIRD SURFACE, 2026-08-30: `/process/` §Confidentiality renders `MEMBERSHIP_ORGS[0]` ("I am a member of the ADR Institute of Canada").** It is rendered from the constant rather than typed, so the sweep this reminder prescribes reaches it — that was `adversarial-reviewer`'s finding and the fix, in that order. **DISCHARGED AS WRITTEN 2026-08-28 — AND RE-ARMED WITH AN EVENT TRIGGER INSTEAD OF A DATE. STILL LIVE.** Pouya re-confirmed all four memberships as current (Q44), which discharges the prohibition this row carried, and `/about/` now publishes the Memberships group. **The row does not close, because he declined renewal-date tracking**, and that was his instruction for what to do about it: *"Without renewal months it cannot fire on a date, so make it fire on an event: re-confirm memberships before any cutover or major republish, and re-stamp §4 when confirmed."* **THE TRIGGER: re-confirm before any cutover, and before any major republish. Then re-stamp §4 the same day.** **His reason, kept verbatim because it is the general principle and not a membership detail:** *"§4 already carries OCNI as lapsed and unpublishable, and that was found roughly a year late. A stamp with no trigger behind it goes stale silently, which is exactly how OCNI got onto a list of things to feature."* **Two things the discharge did NOT license.** (1) **No currency warranty on the page** — list the memberships, promise nothing about their future state; the struck sentence stays struck and nothing replaces it. (2) ~~`memberOf` stays out of the JSON-LD~~ — **SUPERSEDED. Q53, ruled 2026-08-28: EMIT IT.** `/about/`'s Person node now carries the four memberships as `Organization` nodes. Pouya took `adversarial-reviewer`'s argument: they are already crawlable in `/about/`'s HTML, so withholding the triple reduced no exposure and only made the graph less complete than the page. **The consequence for THIS reminder is that it now covers two surfaces** — re-confirming before a cutover means `src/data/schema.ts` as well as the visible list, and they must not be allowed to diverge. **Renewal periods, stated once and not widened again:** the OBA sections and the CTF renew yearly; §4 records **nothing** about ADRIC's or ADRIO's period, and the widened form ("all four renew yearly") reached four files before it was swept. *Previous text described the prohibition and the withheld group; it held for one session and did its job.* | 2026-08-26 | A credential that lapses quietly is the failure mode §4 exists to prevent, and OCNI already did exactly this. The group is on a public page now, which raises the cost of a lapse rather than lowering it — *(This rationale ended by pointing at **Q48** as a possible widening of the row. Q48 closed 2026-08-28 as not site-relevant — ADRIO retention governs whether Pouya keeps a designation, not what the site may say about holding one — so the clause is struck. §12 is read aloud every session; a live reminder pointing at a struck row produces a false surface every time.)*, not just a list |
|
||||
| R11 | **Re-check dependency currency at every phase boundary in the build order** (`docs/01-architecture.md` §Build order, 11 steps). Run `npm view <pkg> version` across **every** pin in `package.json` and compare; do not wait for something to break. Verified does not mean latest — record the reason for any deliberate hold in §7. ✅ **THE STEP-7 RE-ADD TRIGGER IS DISCHARGED, 2026-08-31 — and NOT as written.** It said *"at step 7, put `@lhci/cli` back"*. `@lhci/cli` is still 0.15.1, still `latest`, and still carries 10 findings (7 high) `[verified 2026-08-31]`, so the literal instruction would have re-added a tool with seven high-severity advisories. What shipped is **`lighthouse@13.4.1` + `chrome-launcher@1.2.1`, 0 vulnerabilities**, as `npm run lighthouse`. **The reason is that §7's own advisory attribution was wrong** — it blamed `lighthouse → puppeteer-core → extract-zip`; the carriers were `@lhci/cli`'s own `tmp` and `@puppeteer/browsers`' `extract-zip`, and neither exists in Lighthouse's tree. **The last clause of this trigger is the one that earned its place:** *"if the advisories are still unfixed, that is a decision to take deliberately, not a reason to leave the gap unstated."* They are still unfixed; the decision was taken; §7 records what it costs (no `lhci` assertion config, no server, no run history) and that the gate is local rather than CI, because standalone Lighthouse needs an installed browser and the runner has none. **All six UNAVAILABLE notices are deleted** — `docs/04` (budget table, Performance callout, post-launch checklist), `CLAUDE.md` (performance budget, definition of done), `/build` Phase 5, `docs/06` (PR checks, cutover checklist), `.claude/agents/adversarial-reviewer.md` §4. The **general** half of R11 — re-check every pin at every phase boundary — is untouched and still fires. ✅ **SWEPT AGAIN 2026-09-01, all 19 pins against `npm view`, and TWO MAJORS ARE DEFERRED BY RULING rather than left unstated:** `@astrojs/mdx` **^7.0.8 → 8.0.0** and `typescript` **^6.0.3 → 7.0.2**. Pouya's reasoning — *"npm audit is clean and majors mid-walkthrough add churn without user value"* — with `npm audit` at **0 vulnerabilities** `[verified 2026-09-01]`, which makes it a churn decision and not a security one, **and one that flips the moment that stops being true.** Both now sit on a new **Cutover prep** group at the head of `docs/06`'s cutover checklist, dated, because deferring a thing and forgetting it look identical three weeks later. Four more are a minor or patch behind and already satisfied by their carets, so they need no edit — `astro` 7.2.9 → 7.2.10, `@astrojs/sitemap` 3.7.3 → 3.7.4, `globals` 17.11.0 → 17.12.0, `typescript-eslint` 8.68.0 → 8.69.0; the other 13 are current. **This row is the deferral's reminder, not its replacement** — R11 fires at the next phase boundary regardless | 2026-08-26 | `astro: "^5.0.0"` was recalled rather than checked and was two majors stale the day it was written, which meant a framework carrying high-severity XSS advisories. Between phases is cheap; after a phase of pages is written is not. The build order has ten more boundaries |
|
||||
| R12 | **`compressHTML: true` is a deliberate deviation from the Astro 7 default (`'jsx'`).** Measured 2026-08-26: in an `.astro` template an inline pair split across two lines renders as `<em>a</em><strong>b</strong>` under the default — the space is silently deleted. MDX prose is unaffected | 2026-08-26 | It is a deviation, and undocumented deviations become folklore. Revisit **with a measurement**, not a preference — and re-measure after any Astro major, since the behaviour could change again |
|
||||
| R13 | **The infinity mark ships as a RASTER, and that is temporary. RAISED 2026-08-27; Pouya ruled the committed SVG does NOT close it** — *"Keep it committed, keep the AVIF render path. Your own measurement is the reason: 257 KB wrapping seven embedded base64 PNGs. It renders faithfully because it IS the raster."* So the exception stands and the reminder stays live. `InfinityMark.astro` renders an optimised AVIF/WebP from `src/assets/brand/sml-infinity-mark.png` — a deliberate, documented exception to `docs/02`'s "inline SVG, never a PNG", because the mark is gradient-mesh artwork and no true vector master exists yet (Q38). **Removal trigger: the commissioned vector master lands.** Then replace the `<Picture>` with inline SVG, regenerate `favicon.ico` and `apple-touch-icon.png` from it, and delete the exception from `docs/02`, from the component, and from Q38 | 2026-08-26 | Pouya flagged this himself when he made the ruling: *an interim raster is exactly the kind of temporary measure that becomes permanent by never being raised.* It costs ~8 KB and works, which is precisely why nobody will notice it again. There is no build error to prompt anyone — only this row |
|
||||
| R13 | **The infinity mark ships as a RASTER, and that is temporary. RAISED 2026-08-27; Pouya ruled the committed SVG does NOT close it** — *"Keep it committed, keep the AVIF render path. Your own measurement is the reason: 257 KB wrapping seven embedded base64 PNGs. It renders faithfully because it IS the raster."* So the exception stands and the reminder stays live. `InfinityMark.astro` renders an optimised AVIF/WebP from `src/assets/brand/sml-infinity-mark.png` — a deliberate, documented exception to `docs/02`'s "inline SVG, never a PNG", because the mark is gradient-mesh artwork and no true vector master exists yet (Q38). **Removal trigger: the commissioned vector master lands.** Then replace the `<Picture>` with inline SVG, regenerate `favicon.ico` and `apple-touch-icon.png` from it, and delete the exception from `docs/02`, from the component, and from Q38. ⚠️ **The favicon half of that is now a command rather than a manual step — `npm run icons`, added 2026-09-02** — but it reads the RASTER, so pointing it at a vector master is part of the work this row is waiting for. **It regenerates the favicon only. `apple-touch-icon.png` is still hand-made, and it must stay OPAQUE CREAM when it is remade** — the reason is an external platform behaviour that this repository has not tested, and `docs/reference/brand-assets.md` §The icon set carries it with its provenance. Do not restate it here as a fact | 2026-08-26 | Pouya flagged this himself when he made the ruling: *an interim raster is exactly the kind of temporary measure that becomes permanent by never being raised.* It costs ~8 KB and works, which is precisely why nobody will notice it again. There is no build error to prompt anyone — only this row |
|
||||
| R14 | **Anything a spec makes a claim about must be reachable from the repository.** If the artefact lives only in Drive, in a console, or on someone's disk, no reviewer can check the claim and the claim is unverifiable by construction — see the rule now in `CLAUDE.md` | 2026-08-26 | **This has now cost twice.** Q24 was `AWS-Hosting-Guide.md`, the only record of how the infrastructure was hand-built, living outside the repo. Q32 was the infinity mark: Claude Code traced it from the old site's *loading placeholder*, wrote a scrupulous provenance comment recording exactly that doubt — and **two adversarial review passes still could not catch it**, because the artwork they would have had to compare against was not in the repo. The doubt was correctly stated and structurally uncheckable. That is the failure mode: not a missing warning, a missing artefact |
|
||||
| ~~R15~~ | ✅ **DISCHARGED 2026-08-31 — build step 7b. Seventeen pages and every future article now carry their own generated card; `/` and `/about/` keep the portrait, which Q40 called "not an interim" and which is unchanged.** `src/pages/og/[...slug].jpg.ts` renders them at build from `satori@0.33.4` + `sharp`; `src/data/og-cards.ts` is the registry; `SEO.astro` resolves which of the two kinds a page gets from its own pathname, so the decision lives in one data file rather than in nineteen call sites. `satori` over `astro-og-canvas@0.13.0` — both 0 vulnerabilities `[verified 2026-08-31]` — because `sharp` was already a dependency, so it is one library rather than a CanvasKit wasm blob, and it renders the site's real fonts and tokens instead of approximating them. **The interim note is deleted from `docs/04` and the cutover line from `docs/06`**, which is what this row's trigger asked for. ⚠️ **AND IT SURFACED A HOLE THAT IS WORTH MORE THAN THE ROW: TEXT BAKED INTO A JPEG CANNOT BE GREPPED BY `npm run check:claims`, WHICH UNDER D20 IS THE ONLY PER-STEP CLAIMS CONTROL THERE IS.** A card is the one surface on this site where new prose would face no mechanical check at all. So card copy is constrained structurally rather than editorially: **every headline is its page's own `<h1>`, character for character**, the kicker renders `CREDENTIALS.designations`, and **`npm run og:proof` enforces both against the built HTML** — it fails if the registry and the page disagree in either direction, and it also confirms every `og:image` resolves to a file that exists. That check found a straight-versus-typographic apostrophe on its first run. **A page with no registry entry is a build error, not a fallback to the portrait** — R15's failure mode was never the wrong image, it was the wrong image shipping *invisibly* and reading as intentional, and a silent fallback recreates it exactly. **What is NOT discharged and is deliberately left open:** the check compares strings, so it cannot see a card that renders badly — a headline that overflows, a mark that clips. `npm run og:proof -- --sheet` writes a contact sheet of every card for that, and **a human still has to look**; `docs/06`'s cutover item says so. Two cards were read at step 7b (`/practice/energy/`, the longest headline, and `/practice/insurance/`) and rendered correctly | 2026-08-27 | Nobody on this project will ever see the defect. A link preview is rendered by LinkedIn, Slack and Teams for a reader who is not us, and `docs/04` requires a **unique** title and description per page — nineteen unique pages that all preview identically is the failure, and it is invisible from inside the repo. It cost nothing and worked, which is exactly the R13 shape |
|
||||
| ~~R16~~ | ✅ **DISCHARGED 2026-08-31 — build step 9, which is the trigger this row set. The one-page PDF exists, is committed at `public/pouya-lajevardi-bio.pdf`, and `/about/` links it.** Q45's two open sub-decisions are both taken, and the second one is what makes the first safe. **(a) Generated at build, or authored once? NEITHER — and the third option answers this row's actual objection.** The bio is a PAGE, `src/pages/bio.astro`, so every line of it is reviewed by the same apparatus as every other page: `astro check`, `npm run check:claims` on the built HTML, the per-step code review, and the cutover claims pass. `npm run bio:pdf` then RENDERS the PDF from the built page through Chrome, with **no new dependency** — `chrome-launcher` is already a devDependency because Lighthouse needs it. That is aimed squarely at what this row said the problem was: *"It is the one artefact class this project's review apparatus cannot reach… a PDF circulated with an appointment proposal is read once, by the reader who matters most, and never seen by a reviewer again."* A rendering of a reviewed page is back inside the apparatus. **It is deliberately NOT part of `astro build`** — CI has no Chrome, and a build step that cannot run in CI is Q22's shape again — so the PDF is committed, which R14 wants anyway. **(b) Does it carry anything the site does not? NO, and that is the answer that avoids the §4 question this row flagged.** Every line renders from `CREDENTIALS`, `ROLE`, `BOUTIQUE`, `PRACTICE_AREAS`, `FEES` and `CONTACT`. No matter list — which this row correctly said *"would collide with §4 Forbidden directly"* — no referees, and no figure that is not on `/fees/`. The fee summary IS there, because this row's own reasoning is that an appointment proposal needs the rate card as much as the bio. ⚠️ **`npm run bio:pdf` ASSERTS ONE PAGE and writes nothing if the count is wrong** — counted from the PDF's own page objects, cross-checked against the page tree's `/Count`. A one-page bio that silently becomes two is invisible from the source, because it depends on the print stylesheet, the paper size and how much §4 has grown. **And reading the rendered PDF caught a real breach the source review had not:** the opening sentence read *"I act as a neutral in commercial disputes — as a mediator, as an arbitrator in commercial matters…"*, whose leading clause scoped **mediation**, which Q56 leaves unscoped deliberately. `/bio/` is `noindex` and out of the sitemap: it is a condensed duplicate of `/about/` and `/fees/`. **What is NOT discharged:** nothing in the build regenerates the PDF. It must be re-rendered and re-committed whenever `/bio/`, §4, the rate card or the print styles change, and `docs/06`'s cutover checklist carries that | 2026-08-28 | It is the one artefact class this project's review apparatus cannot reach. A web page is re-reviewed by every audit and re-checked by every build; a PDF circulated with an appointment proposal is read once, by the reader who matters most, and never seen by a reviewer again |
|
||||
@@ -948,6 +948,376 @@ never being raised again.
|
||||
|
||||
# Change Log
|
||||
|
||||
## 2026-09-02 (aq) — The favicon shipped with no transparency and is regenerated from the committed master; Pouya's full-site read-through completes with that as its only finding, and the cutover blockers drop to one
|
||||
|
||||
**Pouya's read-through of every page against §4 is COMPLETE, 2026-09-02.** It is
|
||||
the human half of D20 and it is not delegable. Across 23 pages it returned
|
||||
**one finding, and the finding was not copy**: `public/favicon.ico` had no
|
||||
transparency. His read carried the approvals with it in terms — the
|
||||
`/legal/privacy/` §Who can see it wording, the **SML Company Ltd** consent line,
|
||||
and `/med-arb/` **as shipped** — which discharges the wording sign-off that
|
||||
entry (ap) had routed into this pass.
|
||||
|
||||
### 1. The finding, confirmed before it was acted on
|
||||
|
||||
Pouya's note: *"`public/favicon.ico` has zero transparent pixels; every
|
||||
background pixel is opaque cream (250,247,242,255)."* Confirmed by parsing the
|
||||
ICO container directly rather than by converting it — a conversion step can
|
||||
itself add or drop an alpha channel, and then the measurement is about the
|
||||
converter. All three frames are PNG-encoded and **declare a 32-bit alpha
|
||||
channel**, and then carry `alpha = 255` on every one of their **256 / 1,024 /
|
||||
2,304** pixels, `range = [255,255]`. The ground is `rgba(250,247,242,255)`,
|
||||
which is `--cream` `#faf7f2` exactly. `magick identify` reports these frames as
|
||||
`8-bit sRGB` and says nothing about alpha either way, which is why the container
|
||||
was read directly.
|
||||
|
||||
**A declared alpha channel that is opaque everywhere is the worst shape this
|
||||
defect could take**: every tool reports the file as RGBA and nothing flags it.
|
||||
|
||||
### 2. It is an EXPORT, not a derivation — the R13 branch did not fire
|
||||
|
||||
Pouya's instruction allowed for the harder case: *"If the master itself has no
|
||||
alpha channel, derive the mask from the cream ground and flag R13."* **Measured,
|
||||
and it does not apply.** `src/assets/brand/sml-infinity-mark.png` carries a true
|
||||
alpha channel — 2668 × 1704, **2,272,386 fully transparent pixels (50.0%)**,
|
||||
2,253,091 fully opaque, and **20,795 partial** — the anti-aliased edge of the
|
||||
ribbon, intact. So no mask had to be reconstructed from the cream ground, and
|
||||
nothing here is lossier than the artwork. **R13 is untouched by this** and stays
|
||||
open on its own terms: the mark still ships as a raster.
|
||||
|
||||
The regeneration also **re-derived the documented crop as a side effect**:
|
||||
extracting `{ left: 159, top: 646, width: 2668, height: 1704 }` from
|
||||
`sml-infinity-mark-master.png` returns a buffer **byte-identical** to the render
|
||||
source. `scripts/icons.mjs` asserts that on every run, so the icon stays
|
||||
traceable to committed artwork rather than to a file on a disk — R14.
|
||||
|
||||
### 3. What changed, and what deliberately did not
|
||||
|
||||
`public/favicon.ico`, 16/32/48, **4,857 → 5,365 B**. The ground is now
|
||||
transparent; **nothing else moved.**
|
||||
|
||||
**The composition was measured off the icons as they already shipped rather than
|
||||
re-chosen** — the mark spans **7/8 of the canvas width**, centred on both axes,
|
||||
which is 14/16, 28/32, 42/48 and 158/180 on the touch icon. Regenerating had to
|
||||
reproduce that, not restyle the mark, and it does: composited back onto cream,
|
||||
the mark's bounding box and ink-pixel count are **identical at all three sizes**
|
||||
— 14 × 9 at (1,4), 28 × 18 at (2,7), 42 × 27 at (3,11), ink 92 / 328 / 689.
|
||||
|
||||
⚠️ **`public/apple-touch-icon.png` STAYS OPAQUE CREAM, DELIBERATELY, AND IS
|
||||
BYTE-IDENTICAL ACROSS THIS CHANGE** (`sha256 6377b096…`, asserted before and
|
||||
after). **iOS composites a transparent touch icon onto BLACK**, so the fix for
|
||||
the tab icon is the defect for the home-screen icon. The two requirements are
|
||||
opposite and the asymmetry is now recorded in three places, because the risk is
|
||||
that someone later "fixes" the touch icon to match: `docs/reference/brand-assets.md`
|
||||
§The icon set, the comment beside the `<link>` tags in
|
||||
`src/layouts/BaseLayout.astro`, and R13.
|
||||
|
||||
### 4. Verified two ways, on two instruments that cannot fail the same way
|
||||
|
||||
Pouya asked for both, and asked for the dark ground specifically.
|
||||
|
||||
**(a) Programmatically, on the shipped `dist/` bytes.** The container parsed
|
||||
frame by frame: three frames, dir sizes matching the decoded PNGs, **all four
|
||||
corners `alpha = 0` at every size**, `147 / 636 / 1,499` fully transparent
|
||||
pixels, `15 / 88 / 202` fully opaque, `94 / 300 / 603` partial. The mark is
|
||||
still there and the ground is gone.
|
||||
|
||||
**(b) Composition parity.** Composite the new icon back onto cream and it
|
||||
reproduces the old matted icon to within **1 of 255 on every channel of every
|
||||
pixel** — max delta **1**, mean **0.01–0.02**, **0** pixels over a delta of 8,
|
||||
at all three sizes. That proves the regeneration did not restyle, rescale or
|
||||
reposition the mark.
|
||||
|
||||
⚠️ **AND IT IS NOT A HALO TEST, THOUGH THIS ENTRY'S FIRST DRAFT FILED IT AS ONE
|
||||
— `adversarial-reviewer`, round 1.** The claim written beside it, *"anti-aliasing
|
||||
that had been matted against cream would differ far more than that"*, is the
|
||||
inversion of the truth: **matting against cream is exactly what produces delta
|
||||
0.** A cream-matted edge that has merely had its background knocked out
|
||||
composites straight back to the icon it came from, so both the correct and the
|
||||
defective case pass. **The test returns the same near-zero answer either way and
|
||||
therefore carries no information about the thing it was filed under.**
|
||||
|
||||
**(b2) No halo — the measurement that DOES discriminate.** Read the RGB the
|
||||
partial-alpha pixels actually carry: cream means a halo, the ribbon's own colour
|
||||
means a clean export. **0 of 94 / 300 / 603** partial-alpha pixels are within 12
|
||||
of `rgb(250,247,242)`; the nearest is **13** away, mean distance **174.0 / 178.3
|
||||
/ 177.0**, mean colour ≈ `rgb(118,86,74)`. Maroon-brown, not cream and not black.
|
||||
Run independently after the finding rather than taken from it, and it reproduces
|
||||
the reviewer's figures exactly.
|
||||
|
||||
**(c) Visually, on seven grounds — and then again in a browser.** The frames
|
||||
composited on Chrome dark `#202124`, Firefox dark `#2b2a33`, Safari dark
|
||||
`#3b3b3b`, black, Chrome light `#f1f3f4`, white and cream, at 1:1 and at 5×
|
||||
nearest-neighbour. The old icon shows a hard cream tile on every dark ground and
|
||||
a visible off-white rectangle even on white; the new one shows no tile anywhere.
|
||||
**Then the same page was rendered by Chrome itself** — `<img src="favicon.ico">`
|
||||
on the same grounds, screenshotted headless at DPR 2 — because sharp decoding
|
||||
its own output is one instrument twice. Chrome's ICO decoder honours the alpha
|
||||
on every ground including pure black.
|
||||
|
||||
**(d) Legible on dark AND on light — but NOT at the same cost, and the first
|
||||
draft of this entry recorded it as free.** Two defects, both found by
|
||||
`adversarial-reviewer` round 1 and both re-measured here before being accepted.
|
||||
|
||||
*The figure was wrong.* `rgb(64,22,24)` was quoted as the darkest ink at 32 px
|
||||
and it is **not a painted colour**: exactly one pixel carries it and its alpha is
|
||||
**251**, not 255. The probe filtered at `alpha >= 250` and then computed a
|
||||
contrast ratio as though the pixel were opaque. **A ratio taken from a raw
|
||||
channel value is a claim about a colour that is never painted.** Recomputed on
|
||||
fully-opaque pixels only, composited against the ground: darkest opaque ink at
|
||||
32 px is `rgb(60,29,29)` → **13.62:1** on `#f1f3f4`, not 14.02.
|
||||
|
||||
*The characterisation was worse than the figure.* "Reads as a single-tone loop
|
||||
rather than the full two-tone ribbon" understates it. The maroon lobe composites
|
||||
to **1.04–1.15:1** on `#202124`. Counting pixels reaching 3:1 against each
|
||||
ground, at 16 / 32 / 48 px:
|
||||
|
||||
| | vs `#202124` | vs `#f1f3f4` |
|
||||
|---|---|---|
|
||||
| 16 px | **23** / 256 | 40 / 256 |
|
||||
| 32 px | **87** / 1024 | 168 / 1024 |
|
||||
| 48 px | **189** / 2304 | 378 / 2304 |
|
||||
|
||||
**Roughly half the mark's visible pixels are absent on a dark tab strip, and the
|
||||
previous cream-tiled icon was more legible there.** What the change buys is the
|
||||
removal of that cream rectangle from every dark strip and from white. A
|
||||
`prefers-color-scheme` icon pair does not solve it — that keys off the *page's*
|
||||
scheme, not the browser chrome's. **The trade is recorded as a trade**; it is
|
||||
Pouya's to revisit and §4 of this entry's record is where he would find it.
|
||||
|
||||
**(e) Deterministic.** Three consecutive runs of `npm run icons` produce the
|
||||
identical file, `sha256 3c611a43…`. A generator that drifts would make every
|
||||
later comparison meaningless.
|
||||
|
||||
### 5. An instrument artefact, caught and recorded rather than believed
|
||||
|
||||
The first geometry re-check reported the mark's bounding box on the new 48 px
|
||||
frame as **48 × 48 filling the whole canvas** — which reads as *the regeneration
|
||||
blew the mark up to the full square*, a serious defect. **It is an artefact of
|
||||
the probe.** That probe identifies ink as "differs from cream by more than 6",
|
||||
which is valid on a matted icon and **invalid on a transparent one**: at
|
||||
`alpha = 0` the pixels carry RGB `(0,0,0)`, which differs from cream, so the
|
||||
entire canvas reads as ink. Re-measured by compositing over cream first — so both
|
||||
icons are measured by the same instrument on the same kind of input — the boxes
|
||||
are identical, which is §3 above.
|
||||
|
||||
⚠️ **`CLAUDE.md` lists six instances of *a measurement is a claim about your
|
||||
instrument until you check the instrument*. THIS SESSION ADDED TWO MORE**, and
|
||||
they are different failure modes from each other and from the six:
|
||||
|
||||
- **A probe that is valid on one class of input and silently invalid on
|
||||
another.** "Differs from cream" identifies ink correctly on a matted icon and
|
||||
nonsensically on a transparent one, because `alpha = 0` pixels still carry RGB.
|
||||
Nothing about the probe changed — **the input class did**, and the probe had no
|
||||
way to say so.
|
||||
- **A contrast ratio computed without asking what alpha the colour is painted
|
||||
at.** The 32 px "darkest ink" was a single pixel at `alpha = 251`, filtered in
|
||||
by a `>= 250` threshold and then treated as opaque. **The number was of a
|
||||
colour that is never painted.**
|
||||
|
||||
Both are now in `CLAUDE.md` beside the other six.
|
||||
|
||||
### 6. Records
|
||||
|
||||
- `docs/06-deployment.md` — **the cutover callout drops from TWO blockers to
|
||||
ONE.** ⚠️ **It read ONE earlier the same day and that was a defect**, so the
|
||||
callout now states the difference rather than the number: it said ONE then
|
||||
because an approval had gone **missing** from the list; it says ONE now
|
||||
because the pass that approval was routed into has been **done**. A tally
|
||||
cannot tell those apart. **Q60's waiting period is the last one**, and it is a
|
||||
waiting period rather than a task — earliest useful reading 48 hours, no
|
||||
failure called before 7 days, so it starts first.
|
||||
- `docs/06-deployment.md` — **"Pouya has read every page against §4"** ticked,
|
||||
with the finding and the three approvals it carried recorded on the item. It
|
||||
notes in terms that this does **not** discharge `claims-auditor`'s cutover
|
||||
pass: D20 traded the per-step machine audit for the human pass **plus** one
|
||||
machine pass over the finished site, and one of those two has now happened.
|
||||
- `docs/06-deployment.md` — **"Favicon set complete"** ticked, carrying what was
|
||||
wrong, what fixed it, both verifications, and the touch-icon bar.
|
||||
- `docs/reference/brand-assets.md` — a new **§The icon set**: the two files,
|
||||
their grounds and why they differ, the composition rule, the regeneration
|
||||
command, and the measurements above. Its opening line said *"Every measurement
|
||||
below is `[verified 2026-08-26]`"*, which the new section would have made
|
||||
false, so sections now carry their own stamps.
|
||||
- `AGENTS.md` **R13** — amended in place. Its removal trigger says to regenerate
|
||||
both icons from the vector master when it lands; that half is now a command
|
||||
(`npm run icons`) **but it reads the raster**, so pointing it at a vector master
|
||||
is part of the work R13 is still waiting for. The row also now carries the bar
|
||||
that the touch icon must stay opaque cream when it is remade.
|
||||
- `scripts/icons.mjs` + `npm run icons` — new. **LOCAL ONLY and deliberately not
|
||||
wired into `npm run build` or either deploy path**, like `bio:pdf` and
|
||||
`lighthouse`; verified by grep that no workflow or deploy script calls it. It
|
||||
writes the favicon **only** and never opens the touch icon. It refuses to run
|
||||
if the render source has no alpha channel, asserts the source is still the
|
||||
documented crop of the master, and re-reads the container **from disk** to
|
||||
verify rather than trusting the buffers it just wrote.
|
||||
|
||||
### 7. No new compatibility surface
|
||||
|
||||
Worth stating because it bounds the risk of touching a file every page requests:
|
||||
**the container format did not change.** The previous `favicon.ico` already
|
||||
carried three **PNG-encoded** frames at 32 bpp, and so does this one — same
|
||||
frame count, same sizes, same ascending order, same 6-byte header and three
|
||||
16-byte directory entries, first payload at offset 54. Only the pixel data
|
||||
differs. Anything that could parse the old file parses this one. Verified in
|
||||
Chrome; the remaining browsers are covered by the existing unticked checklist
|
||||
item, **"Tested on iOS Safari, Android Chrome, desktop Safari/Chrome/Firefox"**,
|
||||
which is where the touch icon gets looked at on a real handset.
|
||||
|
||||
### 8. Review — `adversarial-reviewer` only, per D20
|
||||
|
||||
**`claims-auditor` did NOT run.** D20: it runs once, at cutover, over the whole
|
||||
finished site. This change set adds no copy to any page.
|
||||
|
||||
**Round 1: seven findings, none blocking, all seven acted on, none declined.**
|
||||
Two of them — the halo proof and the legibility figures — are recorded above
|
||||
because they were defects **in this entry's own evidence**, which is the class
|
||||
worth keeping. The other five:
|
||||
|
||||
- **The external constants were dressed as repository facts.** `#202124`,
|
||||
`#f1f3f4` and iOS's compositing behaviour are none of them derivable from
|
||||
anything committed, and they sat under a heading promising every figure was
|
||||
read off this repository. **This is R14 from the inside**: a reader cannot
|
||||
re-derive `11.28:1` without a ground colour they have no way to check. The
|
||||
hexes now carry `[observed 2026-09-02]`; the iOS behaviour is attributed to
|
||||
**Pouya's ruling of 2026-09-02** and marked as not re-tested here, with the
|
||||
`docs/06` handset item named as the place that would test it.
|
||||
- **The new stamp mis-dated the render ladders** — and ⚠️ **the fix for this was itself defective; see §11.** The line replacing the file's
|
||||
old blanket date asserted the ladders were `[verified 2026-08-26]`; they carry
|
||||
their own `[measured 2026-08-27]` eleven lines lower. **The fix made a vaguer
|
||||
wrong claim more specifically wrong** — so the header now sends the reader to
|
||||
each section's own stamp and states that a blanket date has already been wrong
|
||||
here once. **Round 2 found that this replacement had removed the only stamp
|
||||
five sections had**; it is now a floor plus overrides.
|
||||
- **The generator wrote the shipped file BEFORE verifying it.** The real defect
|
||||
of the seven. `writeFileSync(OUT, …)` then `verify(OUT)` means a rejected
|
||||
build has already replaced a good favicon, exits 1, and — because the script
|
||||
is deliberately outside the build and both deploy paths — **nothing downstream
|
||||
re-checks**. A developer who fixes the artwork without `git checkout` ships the
|
||||
frames the script rejected, which is the exact failure this change was made to
|
||||
correct. Now it writes `favicon.ico.candidate`, verifies **that**, and renames
|
||||
on success; `die()` removes the candidate on every failure path. **Probed both
|
||||
ways:** forcing the matted-ground check to fail exits **1**, prints the right
|
||||
error, leaves no candidate, and leaves `public/favicon.ico` at its original
|
||||
`sha256 3c611a43…`.
|
||||
- **The iOS rationale was stated in full in four places.** `CLAUDE.md`'s
|
||||
duplicated-fact rule with no mechanism attached — the shape that put a stale
|
||||
SES DKIM table into `docs/05`. `docs/reference/brand-assets.md` §The icon set
|
||||
is now the single owner; the other sites carry the **bar** and a pointer, not
|
||||
the reasoning. `BaseLayout.astro`'s addition went from 6 lines to 3 for two
|
||||
`<link>` elements.
|
||||
- **`npm run icons` was missing from `CLAUDE.md`'s command inventory**, whose
|
||||
next sentence counts the commands. Added, along with the distinction that
|
||||
`bio:pdf` and `icons` are **regenerators rather than gates** — they rewrite
|
||||
committed artefacts, and they stay out of the build because a build should not
|
||||
silently rewrite an artefact a human approved.
|
||||
|
||||
Also removed while in the file: two unreachable `size === 256` branches, a
|
||||
return value no caller used, and a duplicate `metadata()` read.
|
||||
|
||||
### 9. Verification — every command run, every exit status read
|
||||
|
||||
On the committed bytes, `npm run` unless noted:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `check` | **0** — 0 errors, 0 warnings, 0 hints |
|
||||
| `build` | **0** — 23 pages |
|
||||
| `check:claims` | **0** — 12 patterns, every pattern still firing on its fixture |
|
||||
| `check:intake` | **0** — 12 form fields against 12 handler fields |
|
||||
| `og:proof` | **0** — 23 pages, 22 og:image targets, 21 headlines matched their `<h1>` |
|
||||
| `lint` | **0** — eslint + prettier |
|
||||
| `lighthouse` | **0** — worst of 23 **99 / 100 / 100 / 100**, CLS 0.000, run before and after the review fixes |
|
||||
| `icons` | **0**, and byte-identical on three consecutive runs |
|
||||
|
||||
Icon-specific, and the numbers that matter:
|
||||
|
||||
- `public/favicon.ico` **4,857 → 5,365 B**; `dist/favicon.ico` identical to it.
|
||||
- `public/apple-touch-icon.png` **`sha256 6377b096…7de0` before and after**, in
|
||||
`public/` and in `dist/`. Asserted, not assumed.
|
||||
- Corners `alpha = 0` at 16, 32 and 48, read back from the shipped `dist/` bytes.
|
||||
- Ink bbox and ink-pixel count identical to the previous icon at all three sizes.
|
||||
- The failure path probed: a forced rejection exits **1** and leaves
|
||||
`public/favicon.ico` at its original hash with no candidate file behind.
|
||||
|
||||
**Not claimed:** the touch icon has not been looked at on a handset, and the
|
||||
favicon has been rendered in **Chrome only**. Both belong to the existing
|
||||
unticked `docs/06` item *"Tested on iOS Safari, Android Chrome, desktop
|
||||
Safari/Chrome/Firefox"*, which is the right place for them and is not ticked by
|
||||
this entry.
|
||||
|
||||
### 10. What remains
|
||||
|
||||
**Q60 is the only thing blocking the cutover list**, and it is a waiting period
|
||||
rather than a task: TTL is `ENABLED`, no record has yet been watched to expire,
|
||||
`/legal/privacy/` asserts the mechanism and not merely the period, and `docs/09`
|
||||
Part 10's earliest useful reading is **48 hours** with no failure called before
|
||||
**7 days**. **Start it first.**
|
||||
|
||||
`claims-auditor`'s single cutover pass over the finished site is still to run —
|
||||
D20 traded the per-step machine audit for the human pass **plus** that one, and
|
||||
only the human pass has happened.
|
||||
|
||||
### 11. Round 2 — seven should-fix and one consider, all acted on, none declined
|
||||
|
||||
**Stopped at two rounds, per D19.** The cap exists because past two the
|
||||
manufacture rate exceeds the value of the marginal finding; this round is the
|
||||
evidence for the other half of that rule — **five of its eight items were defects
|
||||
in round 1's own repairs.** Recording that, not summarising it away.
|
||||
|
||||
**The one that mattered, and it was mine twice over.** Round 1 established that
|
||||
the composite-onto-cream test cannot detect a halo, and the fix put the
|
||||
discriminating test in the prose. Round 2's point was that **`verify()` still
|
||||
could not run it** — the generator asserted frame count, dimensions, clear
|
||||
corners, "some transparent" and "some opaque", every one of which a cream-matted
|
||||
knockout passes. So the guard went in. **And the first guard was blind too.**
|
||||
|
||||
It counted **partial-alpha** pixels near cream — and a purpose-built haloed
|
||||
fixture has **zero partial-alpha pixels**, because a knockout sets alpha per
|
||||
pixel and leaves none. The rule inspected an empty set, returned 0, and read as
|
||||
a pass. **It passed the real icon and it passed the defect**, which is the
|
||||
uniform-pass shape `CLAUDE.md` warns is the unsurvivable half. Caught by
|
||||
building the fixture before trusting the guard.
|
||||
|
||||
The working rule is the **boundary**: every painted pixel touching a fully
|
||||
transparent one, counted for nearness to cream. Correct frames **1.6 / 1.4 /
|
||||
1.9 %**; the fixture **19.6 %**; gate at **10 %**, ~5× clear of both. Probed
|
||||
both ways — the fixture exits **1** and leaves `public/favicon.ico` at its hash.
|
||||
|
||||
The other seven:
|
||||
|
||||
- **The round-1 stamp fix deleted the floor five sections relied on.** Replacing
|
||||
a blanket `[verified 2026-08-26]` with *"each section carries its own stamp"*
|
||||
was true of two sections and false of five, which then had **no date at all**
|
||||
on 3000×3000, 2668×1704, 257,278 B and the rest. **A vaguer wrong claim was
|
||||
made more specifically wrong.** Now a floor plus overrides — the only shape
|
||||
that covers every section without mis-dating one.
|
||||
- **The `prefers-color-scheme` alternative was dismissed on a mechanism that is
|
||||
wrong.** It does not key off the page's colour scheme; it reports the user's
|
||||
preference, and this site declares `color-scheme` nowhere (`git grep -n
|
||||
color-scheme -- src`, exit 1) — so it resolves to the same preference that
|
||||
makes the tab strip dark. **A wrong reason there is load-bearing**: it is what
|
||||
would stop the next implementer re-examining an accepted cost. The real
|
||||
objections are recorded as **untested**.
|
||||
- **"Roughly half the mark's visible pixels are absent on dark" overstated its
|
||||
own measurement.** They are painted and fall below 3:1, and "visible pixels"
|
||||
(`alpha > 0`) is 109 / 388 / 805, which is not what was counted either. Now:
|
||||
about half as many pixels reach 3:1.
|
||||
- **The iOS constant was hedged in one file and asserted flat in two** — R13 and
|
||||
`docs/06`, the two places a caveat matters most, since §12 is read every
|
||||
session and `docs/06` is what an operator works at cutover. Both now point.
|
||||
- **`docs/06` restated six of the doc's figures and had already diverged**, within
|
||||
one day: it quoted 11.28:1 / 13.62:1 unlabelled, which are the **32 px** row,
|
||||
after a 16 px row (8.82:1 / 12.58:1) was added; and it said "≥3:1 pixels" where
|
||||
the doc said "visible pixels", a different denominator. Cut to a pointer.
|
||||
- **The newly ticked human-pass item still carried an unstruck imperative** —
|
||||
*"START WITH `/legal/privacy/` … IT IS BLOCKER 2"* — on an item recording the
|
||||
pass as done, pointing at a blocker the same page had just closed. Struck.
|
||||
- **`npm run icons` was labelled `LOCAL ONLY`**, which this file defines as
|
||||
*cannot run in CI*. It is pure Node and could; it is out of the build because a
|
||||
build should not silently rewrite an artefact a human approved. Labelled
|
||||
`REGENERATOR`.
|
||||
|
||||
## 2026-09-02 (ap) — `/legal/privacy/` §Who can see it is cut to four plain statements and the mechanics move to the evidence file; the consent checkbox names SML Company Ltd; Q64 closes MOOT rather than answered
|
||||
|
||||
**Two rulings from Pouya, 2026-09-02.** **(1)** §Who can see it **stays
|
||||
|
||||
Reference in New Issue
Block a user