feat: eyebrow 12px → 13px from one definition; confirm and gate the intake TTL
Two of Pouya's rulings of 2026-08-31, committed as one tree at his instruction
("as-is") because AGENTS.md Change Log entry (ai) covers both and splitting it
would mean rewriting the record rather than moving it. This is a deliberate
departure from one-logical-change-per-commit, recorded here rather than left to
be inferred.
THE EYEBROW. `--text-eyebrow: 0.8125rem` added to tokens.css; `.eyebrow` in
global.css retargeted to it. One edit site, which is what the design system
claimed. 13px is not a rung on the modular ladder — it sits between xs and sm
deliberately, because uppercase mono at 0.18em tracking reads smaller than it
measures. `--text-2xs`'s comment stopped calling itself the eyebrow floor.
The rendered sweep (22 pages × 2 widths, 838 mono elements measured over CDP,
not grepped) found exactly one escape and one deliberate override:
- h2.footer-heading, 176 instances — an ESCAPED EYEBROW. Five declarations
byte-identical to `.eyebrow`, differing only in colour. Consolidated to
`class="eyebrow footer-heading"`; the scoped rule is now colour + margin.
The colour is load-bearing, not decorative: `.eyebrow`'s own `--text-meta`
on ink is 3.07:1 and fails.
- span.eyebrow.brand-tagline, 21 instances — HELD at `--text-2xs`. Measured:
at 13px the header grows 81 → 83.4px while `--header-h` is pinned at 81 and
drives `scroll-padding-top`; and with a seventh nav item the CTA lands past
`.header-inner`'s content edge by 42px at 1216, 18px at 1240, 26px at 1280
and 1440. Document overflow is 0 in all of those, so no page-level check
can see it. Insights is that seventh item.
Everything else mono-uppercase is a genuinely different component and was left:
the 0.06em `--tracking-wide` family, the 14px mixed-case designation strip, the
/bio/ print sheet, /contact/'s form labels.
Measured after: contrast unchanged on all 817 instances (11.09 / 8.11 / 5.47 /
5.01:1, all pass at 13px, which is still normal text and needs 4.5:1). Zero
document overflow and identical header geometry at 15 widths. Two eyebrows gain
a line below 414px — /'s hero, already wrapping at 320px before this, and
/insights/'s empty state at 320px only. Accepted, not re-tuned.
docs/02's type spec moved 11–12px → 13px and now enumerates the three 11px
carve-outs instead of implying there are none. The /type-scale/ proof sheet
(d) asked for no longer exists — deleted at build step 2 — so the spec prose is
the proof sheet now.
THE TTL. backend/intake/handler.mjs CONFIRMED to match `AttributeName=ttl`: it
writes `ttl` as a Number, in epoch seconds, at RETENTION_MONTHS = 24. Nothing
needed changing for the enable command.
Removed `|| 0` from the TTL computation. DynamoDB does not expire an item whose
TTL is more than five years past, so `ttl: 0` meant RETAINED FOREVER while
/legal/privacy/ promises deletion — a fallback whose failure mode was the exact
inverse of the claim it was protecting. Unreachable in practice, which is why
it would never have been noticed. A bad value now fails the write.
§7 records TTL as DISABLED at first verification, so the privacy policy's
automatic-deletion promise was unbacked from the moment it was written. §7 is
the only place that status lives; docs/05 and docs/06 carry the constraint and
cite §7, because round 2 of review caught this change set reproducing the SES
DKIM defect — five copies of a status that is about to be re-stamped.
Added, and these are the gate: `TODO(pouya)` on /legal/privacy/'s retention
section, §9 Q60, §12 R19. The page does not publish a period, it asserts a
MECHANISM — deleted by the database rather than by someone remembering — and
nothing in the toolchain can see that. check:claims is frozen with no pattern
for it and deploy does not read docs/06. The copy was NOT softened: it is about
to be true, and weakening a privacy commitment to make it defensible is the
move the rules forbid. What was missing was the gate, not the caveat.
Reviewed by adversarial-reviewer, two rounds (D19 cap), 15 findings, all
accepted, none declined. claims-auditor did not run — D20. Round 1's findings
were almost entirely in prose written that session, and round 2's blocking
finding plus its sharpest should-fix were both defects in round 1's own fixes.
public/pouya-lajevardi-bio.pdf is deliberately NOT in this commit. It was
regenerated and reverted: /bio/'s eyebrow sits inside `.no-print`, so the sheet
has no eyebrow at all, and `cmp -l` showed exactly 10 differing bytes, all in
/CreationDate and /ModDate.
Gates, every one read as an exit status and none through a pipe: check 0,
build 0 (22 pages), check:claims 0, og:proof 0, check:intake 0, lint 0,
minifier tripwire clean, TODO in dist 0. Lighthouse run twice with identical
output: perf 99 on / and 100 on the other 21, a11y 100, best practices 100,
SEO 100 on every indexable page, CLS 0.000, LCP 1.50–2.03s.
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
9f2d2eeb04
commit
64bce105f8
@@ -71,7 +71,7 @@ const aboutLinks = [
|
||||
<div class="footer-grid">
|
||||
<nav class="footer-nav" aria-label="Footer">
|
||||
<div class="footer-col">
|
||||
<h2 class="footer-heading">Practice areas</h2>
|
||||
<h2 class="eyebrow footer-heading">Practice areas</h2>
|
||||
<ul role="list">
|
||||
{
|
||||
PRACTICE_AREAS.map((area) => (
|
||||
@@ -85,7 +85,7 @@ const aboutLinks = [
|
||||
</div>
|
||||
|
||||
<div class="footer-col">
|
||||
<h2 class="footer-heading">Process</h2>
|
||||
<h2 class="eyebrow footer-heading">Process</h2>
|
||||
<ul role="list">
|
||||
{
|
||||
processLinks.map((link) => (
|
||||
@@ -98,7 +98,7 @@ const aboutLinks = [
|
||||
</div>
|
||||
|
||||
<div class="footer-col">
|
||||
<h2 class="footer-heading">About</h2>
|
||||
<h2 class="eyebrow footer-heading">About</h2>
|
||||
<ul role="list">
|
||||
{
|
||||
aboutLinks.map((link) => (
|
||||
@@ -112,7 +112,7 @@ const aboutLinks = [
|
||||
</nav>
|
||||
|
||||
<div class="footer-col footer-contact">
|
||||
<h2 class="footer-heading">Contact</h2>
|
||||
<h2 class="eyebrow footer-heading">Contact</h2>
|
||||
<ul role="list">
|
||||
<li><a href={`mailto:${CONTACT.email}`}>{CONTACT.email}</a></li>
|
||||
<li><span class="footer-meta">{CONTACT.phoneFallback}</span></li>
|
||||
@@ -207,12 +207,10 @@ const aboutLinks = [
|
||||
gap: var(--space-7) var(--space-6);
|
||||
}
|
||||
|
||||
/* Type comes from the global `.eyebrow` class on the element; only what differs
|
||||
is here. The colour is load-bearing, not decorative: gold-l on ink is
|
||||
11.09:1 and `.eyebrow`'s own `--text-meta` on ink is 3.07:1, which fails. */
|
||||
.footer-heading {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-medium);
|
||||
letter-spacing: var(--tracking-eyebrow);
|
||||
text-transform: uppercase;
|
||||
color: var(--text-inverse-2);
|
||||
margin-block-end: var(--space-4);
|
||||
}
|
||||
|
||||
@@ -190,14 +190,25 @@ const isHome = path === '/';
|
||||
gap: var(--space-05);
|
||||
}
|
||||
/* The tagline appears only where there is room for it — see the 76rem block.
|
||||
Measured: at 11px with 0.18em tracking the string is ~285px wide, and
|
||||
restoring it under the name pushed the one-row header past its content box
|
||||
by 18px at 1024 with six items and 84px with seven. The brand name carries
|
||||
Measured: at 11px with 0.18em tracking the string is 283.1px wide. (Two
|
||||
figures for 1024px were struck 2026-08-31: below 66rem `.nav` takes
|
||||
`flex-basis: 100%` and `.header-cta` is `display: none`, so there is no
|
||||
one-row header there and no CTA box to be past — neither number could be
|
||||
re-derived. See the rule at the 66rem block below.) The brand name carries
|
||||
the identity on its own; the tagline is a flourish, and `/` opens with the
|
||||
same words as the hero eyebrow (docs/01). */
|
||||
.brand-tagline {
|
||||
display: none;
|
||||
font-size: var(--text-2xs); /* 11px — the eyebrow floor in docs/02 */
|
||||
/* HELD AT 11px, BELOW THE `.eyebrow` THIS ELEMENT CARRIES. Two reasons, both
|
||||
measured 2026-08-31 at 13px. (1) `--header-h` is 81px and the header goes
|
||||
to 83.4px — `global.css` drives `scroll-padding-top` from that token. (2)
|
||||
With a seventh nav item the CTA lands past `.header-inner`'s content edge
|
||||
by 42px at 1216, 18px at 1240 and 26px at 1280 and 1440 — it does not
|
||||
resolve as the viewport widens. Insights is that item; `showInsights`
|
||||
turns it on at two published articles. ⚠️ Document overflow is 0 in every
|
||||
one of those cases: flex lets the CTA sit past the padding edge without
|
||||
growing the document, so a page-level check cannot see this. */
|
||||
font-size: var(--text-2xs);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.brand-name {
|
||||
@@ -385,8 +396,9 @@ const isHome = path === '/';
|
||||
.nav-list {
|
||||
flex-wrap: nowrap;
|
||||
flex: none;
|
||||
/* 16px from 66rem, widening to 24px at 76rem where there is room for it.
|
||||
Measured with seven items at every width from 1024px up. */
|
||||
/* 16px from 66rem, widening to 24px at 80rem — see that block; the 76rem
|
||||
block deliberately does NOT widen it. Measured with seven items at every
|
||||
width from 1024px up. */
|
||||
column-gap: var(--space-4);
|
||||
}
|
||||
.header-cta {
|
||||
|
||||
@@ -77,7 +77,7 @@ const { preloadSerifItalic = false, ...seo } = Astro.props;
|
||||
a fallback first, and a serif-to-fallback swap inside a 96px headline
|
||||
moves the whole last line.
|
||||
|
||||
GEIST MONO IS DELIBERATELY NOT. It sets the eyebrow — 12px, uppercase,
|
||||
GEIST MONO IS DELIBERATELY NOT. It sets the eyebrow — 13px, uppercase,
|
||||
0.18em tracking — and the credential labels. A swap there costs one short
|
||||
line of reflow at a size where the fallback is metrically close, and
|
||||
preloading it would put 95,688 B of font on the critical path instead of
|
||||
|
||||
@@ -906,7 +906,8 @@ const CREDENTIAL_GROUPS = [
|
||||
not a heading and never carries the <h*>." `Eyebrow.astro` restates it.
|
||||
|
||||
The measurable consequence was worse than the rule breach: at 11px these
|
||||
<h3>s were SMALLER than the 12px eyebrow above them and 5px smaller than
|
||||
<h3>s were SMALLER than the eyebrow above them (12px at the time, 13px
|
||||
since 2026-08-31) and 5px smaller than
|
||||
the 16px list items they head, so "MEMBERSHIPS" was the least legible text
|
||||
on the page an appointing body reads.
|
||||
|
||||
|
||||
@@ -37,10 +37,13 @@
|
||||
* Neither is a fact a reader needs, and neither is verified.
|
||||
* - **Automatic deletion IS stated, and it is the one promise on this page
|
||||
* that depends on a control nobody has verified.** The handler writes the
|
||||
* `ttl` attribute; TTL must also be ENABLED on the table, which §7 does not
|
||||
* record. docs/05's definition of done carries "TTL set and verified by test
|
||||
* record" and `docs/06`'s cutover checklist now names this page as what that
|
||||
* item is protecting. It must be verified before this page is public.
|
||||
* `ttl` attribute, and ⚠️ **writing the attribute is not the mechanism** —
|
||||
* TTL must be ENABLED on the table, and **§7 records whether it is.**
|
||||
* docs/05's definition of done carries "TTL set and verified by test
|
||||
* record" and `docs/06`'s cutover checklist names this page as what that
|
||||
* item is protecting. **Both halves — `ENABLED` by command, and a test
|
||||
* record confirmed deleted — before this page is public.** See the
|
||||
* TODO(pouya) on the retention section below, and §9 Q60.
|
||||
*
|
||||
* ⚠️ **NO LICENSURE CLAIM AND NO ANSWER TO THE CAPACITY QUESTION.** A privacy
|
||||
* policy is where "legal advice" phrasing arrives by convention. §4 records
|
||||
@@ -205,6 +208,15 @@ const COLLECTED = INTAKE_FIELDS.map((field) => field.label);
|
||||
</p>
|
||||
|
||||
<h2>How long it is kept</h2>
|
||||
{
|
||||
/* TODO(pouya): is DynamoDB TTL `ENABLED` on the intake table, and has a
|
||||
test record been written with a near-future `ttl` and observed to
|
||||
disappear? AGENTS.md §9 Q60. The sentence below asserts a mechanism,
|
||||
not just a period — `describe-time-to-live` returned `DISABLED` on
|
||||
2026-08-31 — and it must not go public until both halves are
|
||||
confirmed. §7 records the status; do not answer this from the
|
||||
handler code, which only writes the attribute. */
|
||||
}
|
||||
<p>
|
||||
<strong>{RETENTION_MONTHS} months from the date you send it</strong>,
|
||||
after which the record is deleted automatically by the database rather
|
||||
|
||||
@@ -200,15 +200,17 @@ h4 {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* The class is the type treatment; `Eyebrow.astro` is the label component. A real
|
||||
heading may carry the class — the footer's columns do — but an eyebrow above a
|
||||
heading is never itself an <h*>, which is what the component enforces. */
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
font-size: var(--text-eyebrow);
|
||||
font-weight: var(--weight-medium);
|
||||
letter-spacing: var(--tracking-eyebrow);
|
||||
text-transform: uppercase;
|
||||
color: var(--text-meta);
|
||||
}
|
||||
/* An eyebrow is a label, never the page's heading element. */
|
||||
.eyebrow .dot {
|
||||
display: inline-block;
|
||||
inline-size: 6px;
|
||||
|
||||
@@ -64,10 +64,15 @@
|
||||
|
||||
/* Fluid scale, 360px → 1600px viewport. Ratio widens toward the display
|
||||
end (1.25 → 1.333) so headlines scale harder than body copy.
|
||||
--text-2xs is the eyebrow floor docs/02 sets at 11px. Added 2026-08-27:
|
||||
SiteHeader wrote `0.6875rem` as a literal, step-1 review finding #7. */
|
||||
--text-2xs: 0.6875rem; /* 11 — eyebrow */
|
||||
--text-2xs is the 11px floor for a COMPRESSED label — the header tagline
|
||||
and the print sheet. It stopped being the eyebrow floor on 2026-08-31.
|
||||
Added 2026-08-27: SiteHeader wrote `0.6875rem` as a literal, step-1
|
||||
review finding #7. */
|
||||
--text-2xs: 0.6875rem; /* 11 — label */
|
||||
--text-xs: 0.75rem; /* 12 — legal */
|
||||
/* Not a rung on the ladder above: 13px sits between xs and sm because
|
||||
uppercase mono at 0.18em reads smaller than it measures. Pouya, 2026-08-31. */
|
||||
--text-eyebrow: 0.8125rem; /* 13 — eyebrow */
|
||||
--text-sm: 0.875rem; /* 14 — meta */
|
||||
--text-base: 1rem; /* 16 — body */
|
||||
--text-lg: clamp(1.0625rem, 0.99rem + 0.32vw, 1.1875rem); /* 17→19 */
|
||||
|
||||
Reference in New Issue
Block a user