Skip to content

Illustrate Series Three: 50 portraits, 100 faces, per-card pages - #11

Merged
ralyodio merged 1 commit into
masterfrom
series-three-art
Sep 8, 2026
Merged

Illustrate Series Three: 50 portraits, 100 faces, per-card pages#11
ralyodio merged 1 commit into
masterfrom
series-three-art

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Series Three shipped in #10 as a roster with no art. This renders the whole set and turns on its card pages.

Every face on /security-professionals is now a real card, and each of the 50 has a page at /security-professionals/<slug> with a 1200x630 OG composite.

The pipeline, and why it skips enhance

scripts/security-pros.mjs is a sibling of the Series Two pipeline, with one deliberate difference: all runs portraits -> render -> publish and does not run enhance.

On Series Two that image-to-image "premium finish" pass rewrote text on the card faces despite the prompt forbidding it — it fabricated quotes attributed to living people, invented stat panels that were not on the card, and misspelled a name. Nothing in the pipeline catches that, because there is no OCR step. This set's whole premise is documented, sourced history, so an invented quote attributed to a real person is the worst defect it can carry.

enhance now refuses to run unless ENHANCE_I_WILL_CHECK_EVERY_FACE=1 is set. All 100 faces in this PR are the text-accurate HTML render.

Template changes, from the same finding

  • The curator's note is a labelled panel, not a quotation. Series Two wraps the note field in quote marks, which on the printed back reads as something the subject said. It is editorial voice.
  • The portrait window is 72%, was 58%. At 58% the caption band left a dead third of black on the front — visible on the very first render, and exactly the kind of empty region the finish pass fills with invented content.

Bug fixed along the way

src/data/hacking.ts and security.ts now import "./roster.ts" with an explicit extension. My refactor in #10 onto the shared roster module left an extensionless specifier, which Node's type stripping cannot resolve — that broke node scripts/hacking-legends.mjs for Series Two as well as the new pipeline. tsconfig gains allowImportingTsExtensions, legal because noEmit is set. Both pipelines validate again.

Also

  • 50 source portraits committed under assets/portraits/security-pros/, losslessly recompressed 82MB -> 30MB, so pnpm security render regenerates every face with no API calls (the Series Two precedent, and the thing that makes a text-accurate re-render free).
  • pnpm security / security:validate / security:render scripts.
  • Roster page copy is art-aware and now reads as a finished set.
  • Editorial fix: Grossman's curator's note referred to "the request", which means nothing to a reader.

Verified

  • npx tsc --noEmit clean; pnpm build clean
  • 165 -> 265 prerendered pages: 50 card pages + 50 OG images added
  • Rendered roster HTML: 0 "art pending" plates, 50 card-page links, 50 new sitemap entries
  • node scripts/security-pros.mjs validate and hacking-legends.mjs validate both pass
  • Faces eyeballed rather than assumed — spot-checked fronts and backs for correct names, real stat values, and no fabricated quotes

🤖 Generated with Claude Code

https://claude.ai/code/session_014htrUgbnoKQt9iT4G7hUpy

Series Three shipped as a roster with no art. This renders the whole set and
turns on its card pages.

The pipeline is scripts/security-pros.mjs, a sibling of the Series Two one, with
one deliberate difference: `all` runs portraits -> render -> publish and does NOT
run `enhance`. On Series Two that image-to-image finish pass rewrote text on the
card faces despite the prompt forbidding it — fabricated quotes attributed to
living people, invented stat panels that were not on the card, and a misspelled
name. No OCR step exists to catch it. This set's premise is documented, sourced
history, so an invented quote is the worst defect it can carry. `enhance` now
refuses to run without ENHANCE_I_WILL_CHECK_EVERY_FACE=1, and all 100 faces here
are the text-accurate HTML render.

Two template changes follow from the same finding:

- The curator's note renders as a labelled panel, not wrapped in quote marks. On
  Series Two backs the quoted note reads as something the subject said; the field
  is editorial voice.
- The front's portrait window is 72% rather than 58%. At 58% the caption left a
  dead third of black — visible on the first render, and exactly the kind of empty
  region the finish pass fills with invented text.

Also fixed: `src/data/hacking.ts` and `security.ts` import "./roster.ts" with an
explicit extension. My earlier refactor onto the shared module left an
extensionless specifier, which Node's type stripping cannot resolve — that broke
`node scripts/hacking-legends.mjs` for Series Two as well as the new pipeline.
tsconfig gains allowImportingTsExtensions, legal because noEmit is set.

The 50 source portraits are committed under assets/portraits/security-pros/,
losslessly recompressed 82MB -> 30MB, so `pnpm security render` regenerates every
face with no API calls.

Adds /security-professionals/<slug> pages with 1200x630 OG composites, `pnpm
security` scripts, and the sitemap entries the art unlocks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014htrUgbnoKQt9iT4G7hUpy
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

4 finding(s)

MEDIUM: 4

Severity Rule Location
MEDIUM js-credential-logged scripts/migrate.mjs:16
MEDIUM js-unescaped-html-sink src/app/cards/[slug]/page.tsx:60
MEDIUM js-unescaped-html-sink src/app/hacking-legends/[slug]/page.tsx:68
MEDIUM js-unescaped-html-sink src/app/security-professionals/[slug]/page.tsx:66

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 6942622 into master Sep 8, 2026
4 checks passed
@ralyodio
ralyodio deleted the series-three-art branch September 8, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants