Repository navigation
Names and caption fonts: eleven caption styles in both builders, every Latin letter they draw, provider names kept whole - #844
Conversation
David approved the plan on 2026-11-03 with the picker in both builders and eleven caption faces chosen from a measured shortlist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
…ared list Bundles the eleven caption faces David chose (static weights; the three cut from variable fonts renamed, as the open font licence requires for a modified copy) with their licences, adds the shared catalogue in api-zod, registers every face on the server, draws a catalogue id at its fixed weight, refuses a render when the chosen face did not register, and keeps every stored font string drawing exactly as before. Requests may name only a catalogue face; stored recipes keep their old strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
…ovider names kept whole or not at all The admitted letters are now derived from the shipped caption fonts (349: adds Vietnamese, Romanian and Turkish; drops the obsolete ʼn and ſ, which several faces lack) and pinned in a generated file; a test fails when the pin and the fonts disagree in either direction, and every admitted character and its uppercase is drawn in every face. A Google or Apple name is stored only when the whole name passes the rule, so nothing garbled is ever kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
…n face Replaces the wizard's five-entry font list (four of which saved in a fallback face) with the eleven-face picker, adds the same picker to the meme page's remix builder, loads only the chosen face from the file the server draws with, draws the preview only once it has loaded and holds save and download until then, offers a retry when a face fails to load, stamps the face id on every save and download, and moves a restored draft's old font stack to the nearest face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
…er names whole Onboarding's name field now says to write the name in English letters, the way it should appear on memes, since a Google or Apple name the rule can't hold is no longer stored. A Google sign-in test drives a refused first name, an accepted surname, and a later sign-in that never overwrites either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
The picker loads and reports its own face's status, so both builders just place it; a restored draft's old font is normalized where every read and save already normalizes it, not again at restore; the Anton-only path helper gives way to the general one; the default caption face is preloaded so the preview's first caption is not a round trip late; two stale comments corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d8a7cd4968
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Astra — round 1Assessed at Oracle met at this head: no A temporary font-download failure can leave someone unable to make their meme, with no visible explanation or retry. The approved plan explicitly promises that recovery. I recommend a bounded interface correction using the existing loader; the overall font approach does not need replacing. No new product decision is needed from David. Finding Direct inspection confirms the reported path. Step2Image.tsx reduces the font status to readiness and disables saving until ready. AdvancedOptionsSheet.tsx starts closed and mounts the picker only when opened. That picker contains the failure explanation and retry. The preview also withholds captions until the font is ready, without displaying an explanation itself. The bounded failure class is a required resource blocking the main action while its recovery controls exist only inside optional, collapsed settings. It covers the initial default font, a restored selection, and a newly selected font whose panel is closed before loading fails. A failed network request is sufficient; no unusual user behavior is needed. Opening Advanced options can recover, but the interface gives users no reason to discover that workaround. Preventing this unexplained interruption is worth the small correction, particularly because visible recovery is an explicit settled requirement. The underlying captionFontLoader.ts already exposes status and retry. I recommend presenting those outside the collapsible content, near the preview or blocked action, while keeping an understandable route to choose another style. Preserve the selected font and draft, and keep saving unavailable until the selected font loads: allowing a fallback would defeat the agreed preview/save match. This needs no separate recovery state or automatic retry system. The remix builder already mounts its picker outside a collapsible panel; inspection does not establish the same defect there. For verification, demonstrate that:
The inspected existing failure test mounts the picker directly, so it does not establish recovery in the collapsed wizard. My conclusion is based on code inspection, not a browser reproduction; focused verification of the composed wizard would address that gap. I recommend Claude make this correction and verify those outcomes, then continue through the existing review and required checks. Generated by Claude Code |
Fable — round 1Assessed at Oracle met at this head: no ReadoutIn the meme wizard — the main "Make my meme" path — if the chosen lettering style fails to download, the button goes grey and the preview shows the photo with no words, and nothing on screen says why or offers a way out. The "try again" and the other styles exist, but only behind "Advanced options", which starts closed, and a visitor has no cue to look there. The approved plan says a failed load shows a Retry; in the wizard it does not until the visitor guesses. That is why the gate reads "no" — one named requirement, in one of the two builders, in a failure path; the remix builder on the meme page is fine because its style picker is always on screen. I recommend fixing it in this round: one screen, a small change (show the same "didn't load — try again" line where the visitor is already looking), plus a check that mounts the actual wizard step, since the existing check looked only at the picker on its own and so passed while the wizard had this gap. One consequence to know about, not to decide: a visitor whose browser refuses font downloads altogether can no longer make a meme in either builder, because the agreed rule is that a saved meme must match its preview, so nothing is saved in a stand-in face. The plan's preview-loading decision covers that trade; the fix at least tells such a visitor what happened. Assessment
|
…zard's collapsed panel The wizard keeps its style picker under Advanced options, which starts closed, so a caption face that failed to load left Make my meme disabled with no reason and no retry on screen. A shared status line now says the style is loading or didn't load, with Try again, beside the wizard's save area; the picker uses the same line. The old-recipe check no longer pins image hashes from one machine, which failed on CI's: it checks the canvas font string is the one the earlier code built, that Impact is still Anton, and that an old picker stack still draws as Impact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ
|
@codex review Generated by Claude Code |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Workstream: #719
What & why
The names-and-fonts plan David approved on 2026-11-03, built as one increment.
Before this, four of the wizard's five fonts saved in a fallback face that didn't match the preview, the meme page's builder had no font choice, and a Google name like "Nguyễn" was stored as "Nguy n".
Approved-plan oracle
Oracle source:
docs/plans/PLAN_NAMES_AND_FONTS.md, sha2565856af1e38c6cb42a73c4f339aa24c9b379fccd47f91de4252876ffbdf531b6c(committed on this branch).Direction: n/a — the plan cites David's 2026-11-02 English-script decision in
decisions.md, quoted in its oracle block.Product intent:
Must not change:
Settled decisions:
The eleven faces (David, 2026-11-03, from 75 Google Fonts measured with each file's own character map,
fc-query -i 0, and a page showing each face with sample names and memes). All are free, open-licensed Google Fonts:blocksansserifslabſ Ǎ ǎ Ǔ ǔcomicʼn ſ Ǎ ǎ Ǔ ǔstencilʼn ſ Ǔ ǔroundedſhandsketchſscriptterminalſ Ǎ ǎ Ǔ ǔDropped after measurement: Bebas Neue and Oswald-like condensed faces (too close to Anton), Pacifico (lacks
IJ ij ſ), and every horror, pixel, western, marker and graffiti face measured, each of which lacks 100 or more letters including all of Vietnamese.Two of today's letters leave the rule:
ʼnandſ(David, 2026-11-03). Both are obsolete (ʼnwas deprecated by Unicode;ſleft English print around 1800), Bangers and Black Ops One lackʼn, and five of the faces lackſ. David accepted this in choosing the eleven. No stored name holds either (Phase 2's check found no name outside the rule; an account with one would meet the field's refusal on its next edit, as any refused character does).The admitted letter set is derived from the font files, not written by hand (Astra, rounds 1 and 2). A candidate character must have Unicode General_Category Letter and Script Latin. It is admitted when every chosen face contains it, and contains every code point of its JavaScript uppercase mapping, the form the caption renderer's all-caps draws (
toUpperCase,memeGenerator.ts:329,LivePreview.tsx:161). Measured 2026-11-03 on the eleven files, the result is 349 characters:U+0041–U+005A U+0061–U+007A U+00AA U+00BA U+00C0–U+00D6 U+00D8–U+00F6 U+00F8–U+0148 U+014A–U+017E U+01A0–U+01A1 U+01AF–U+01B0 U+01FE–U+01FF U+0218–U+021B U+0237 U+1E80–U+1E85 U+1EA0–U+1EF9That is:
ʼnandſ(above);Ǿ ǿ Ẁ ẁ Ẃ ẃ Ẅ ẅand the ordinal indicatorsª º, among others.ƒis out, because its capitalƑis missing from several faces; so are the pinyin lettersǍ ǎ Ǔ ǔ.How the set is kept honest:
eplus a combining mark, after NFC.One shared caption-font catalogue in
lib/api-zod, which both the browser and the server import (Astra, scope NF2). Each entry has:OH Block;How the catalogue is used:
textOptions.fontFamilycarries an id for new memes. The schema accepts the eleven ids, or a legacy string on a stored recipe only."<internal>", sans-serif.@font-faceunder the same internal name.The preview and the saved meme never differ in face or weight (Astra, round 1).
document.fonts.load). While it loads, save and download are unavailable. If loading fails, the selection and the draft are kept, a Retry appears, and any other face can be chosen. Save and download return once the face shown matches the selected id.memeGenerator.ts:48); the missing-packaged-font bug of PR Fix #830: ship the meme font with the built server #831 was exactly that.boldas normal and the server treats it as bold (LivePreview.tsx:123,memeGenerator.ts:307).Restored wizard drafts are moved to ids (Astra, round 1). A wizard draft restored from storage (
wizardStorage.ts, kept up to an hour) that holds an old picker's CSS stack has its font mapped to the nearest catalogue id before preview or save; the rest of the draft is untouched, and the picker shows the mapped choice. New API requests accept ids only.Legacy font strings keep today's interpretation (Astra, scope NF4). A stored recipe with an omitted
fontFamilyalso keeps today's default face and weight; the new catalogue default (Anton, block) applies to new memes only. A stored recipe whosefontFamilyis not a catalogue id resolves exactly as the renderer does today. That path is the legacy recipe render (GET /memes/:slug/imagefor an unbaked row or a missing file) and the boot backfill. Because the internal family names are new, a legacy string such as'Bebas Neue', Impact, sans-serifcan never start selecting a newly bundled face. A test renders a legacy-string recipe before and after and compares the bytes.Provider names: whole value or nothing (Astra, scope NF3).
cleanProvidedPersonalNameis replaced byacceptProvidedPersonalName:The correction surface is onboarding's existing required display-name field. Its guidance line reads "Write your name in English letters — the way you'd like it on your memes." No correction-state subsystem and no stored rejected text.
The picker in both builders (Question N1, answered): the wizard's sheet, and a new control in the remix builder on the meme page, both rendering the one catalogue.
Decisions taken on my recommendation
weightinlib/api-zod/src/captionFonts.tsand re-cut the file.CaptionFontPicker.tsx.captionFonts.ts.index.html.CaptionFontStatusinStep2Image.tsx.withCaptionFontUnregisteredForTestand its test.Latitude granted to me: none.
Pre-PR quality pass:
/simplifyran (reuse, simplification, efficiency, altitude); its fixes are commit d8a7cd4.Review round 1 (0417c42): the wizard's font failure is now explained, with its retry, outside Advanced options.
Specialist review: the sign-in change (provider names) is in the auth class, so Codex's security review on this PR is its specialist review.
Verification
pnpm test, four shards), on the branch withmainmerged in: 3,632 tests, 0 failed.IJ, and refuses Greek,ƒ,ſ,ʼnandǎ.npx vitest run): 100 files, 1,046 tests passed. Each face loads from the server's file under its family and weight; the preview waits for the face; a failed load offers Try again; in the real wizard, with Advanced options closed, a failed face shows its notice and Try again; saves always send a face id; an old draft previews and saves in the mapped face; onboarding shows the English-letters line.pnpm run typecheck: clean.pnpm run check:codegen-drift: clean.Post-merge verification (live environment)
UAT
Captions now come in eleven styles in both builders, and names can hold Vietnamese, Romanian and Turkish letters. The machine checks run in CI; the steps that are yours are how the styles look and whether the meme page's picker feels right.
Setup
Steps
1. Each style looks right on a meme with an accented name
Do: On a fact page on the Repl, open the builder, set your name to "Zoë Nguyễn", pick a photo, open Advanced options and try each caption style; save or download one meme in two or three styles.
Expect: Each style looks distinct and suits a meme, the accents sit correctly, and the saved meme looks the same as its preview.
Lane: human
Judgement: Whether each style looks good on a real meme, and whether the saved image matches what you saw, is a visual judgement only you can make.
2. The meme page's builder offers the same styles
Do: Open a shared meme's "make it your own" builder and look below the preview.
Expect: The same eleven caption styles appear, and choosing one changes the preview.
Lane: human
Judgement: Whether the picker feels at home in that builder is your call.
3. Onboarding asks for the name in English letters
Do: Run the onboarding screen tests.
Expect: The name field's guidance reads "Write your name in English letters — the way you'd like it on your memes."
Lane: ci
Check:
artifacts/overhype-me/src/pages/Onboard.test.tsx4. Every face ships, renders distinctly and refuses to fall back
Do: Run the caption font tests.
Expect: Every face is identical on the server and the web app, each renders differently, and an unregistered face refuses to render.
Lane: ci
Check:
artifacts/api-server/src/__tests__/captionFonts.test.ts5. The name rule matches the fonts
Do: Run the coverage and rule tests.
Expect: The pinned letters equal those the fonts draw, every admitted letter and its capital renders in every face, and the shared table passes.
Lane: ci
Check:
artifacts/api-server/src/__tests__/nameRuleFontCoverage.test.ts6. Google names are kept whole or not at all
Do: Run the Google callback tests.
Expect: A refused first name is not stored, an accepted surname is kept whole, and a later sign-in never overwrites a stored name.
Lane: ci
Check:
artifacts/api-server/src/__tests__/routes.google.callback.test.tsRegression
R1. Old memes keep their font
Do: Run the caption font tests.
Expect: Stored recipes with no font, "Impact", or an old Bebas Neue stack get the same canvas font as before and still draw in Anton.
Lane: ci
Check:
artifacts/api-server/src/__tests__/captionFonts.test.tsR2. The browser's builders load and wait for the chosen face
Do: Run the browser caption font tests.
Expect: The preview waits for the face, a failed load offers Try again in both builders (in the wizard without opening Advanced options), and every save sends a face id.
Lane: ci
Check:
artifacts/overhype-me/src/components/meme-builder/__tests__/captionFonts.test.tsxNot bugs
ʼnandſ, are no longer allowed in names; David accepted this when choosing the eleven fonts.🤖 Generated with Claude Code
https://claude.ai/code/session_01UMMrrrX1dR5EdX15XJxkAZ