Repository navigation
feat: v1.1.0 "Indigo" — focal point enforcement, named versions, staff perf - #157
Merged
Merged
Conversation
…f perf
Five changes shipped as one release.
1. Photo features refuse to publish without a focal point
The photofeature hero fills the viewport with object-cover, so it always
discards part of the photo. utils/focalPoint.ts already uses media.focalX /
focalY to choose which part survives, but every image in the database sits at
Payload's 50/50 default, so in practice every hero still cropped from dead
centre and took faces first.
A beforeChange hook now blocks the publish and explains how to fix it: open
Media, drag the focal-point marker onto the subject's face, save, publish
again. The message names the offending image.
Payload writes 50/50 for any upload whose point was never moved, so
"untouched" cannot be distinguished from "deliberately centred". Rather than
deadlock an editor who genuinely wants a centre crop, articles gain
focalPointAcknowledged — ticked once, it lets that article through.
Strictly scoped to photo features. Every other path returns before touching
anything: normal articles are unaffected, draft saves are never blocked, and
the legacy import short-circuits first. Verified across all five cases.
2. Photofeature credit matches the gallery treatment
Was text-white/50, plain text, single photographer. Now shares resolveCredit
with galleries, so credit resolves in the same order (explicit credit, media
photographer, write-in), supports several photographers on one line, and links
staff to their profiles. Fixed white rather than sampled: the hero always
carries a dark bottom gradient, so there is nothing black would ever suit.
3. Named versions, and 1.1.0
lib/version.ts derives a name from the major version and keeps it for that
whole major line — 1.0.0, 1.1.0 and 1.14.3 are all "Indigo". Naming the next
major is a one-line edit. Rendered in the footer as v1.1.0 "Indigo".
4. Staff profiles were slow and felt unclickable — two independent causes
getPhotoArticleMap paginated every published article, 50 at a time, selecting
the full lexical `content` body, looping until every portfolio photo resolved.
For prolific photographers many photos are never used inline, so it walked the
entire articles table — thousands of rows once the legacy archive counts — on
every render. Featured images are an indexed relationship, so they are now
resolved with one `featuredImage: { in: [...] }` query that does not read
`content` at all. Only genuinely inline-only photos fall through to the scan,
which drops to depth 0 and stops after 1,000 articles; a photo older than that
renders without a link rather than holding up the page.
Separately, the portfolio grid sent legacy-archive photos through Next's image
optimizer. Those resolve to /archive/*, served by a different upstream that
the optimizer's loopback fetch cannot reach, so the requests hang. A prolific
photographer's grid is mostly archive photos, which is what made the page feel
dead. RichTextParser already guards inline uploads this way; the grid now does
the same.
5. Migration
articles.focal_point_acknowledged plus its _articles_v shadow, registered in
both paths per docs/migrations.md. Defaulted to false, so no backfill. Applied
cleanly via the TS path on a fresh database, and the production SQL path ran
twice against a populated one to confirm it is idempotent.
pnpm typecheck clean. pnpm lint 0 errors (53 pre-existing migrations/
warnings, unchanged).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five changes, one release.
1. Photo features refuse to publish without a focal point
The hero fills the viewport with
object-cover, so it always discards part of the photo.utils/focalPoint.tsalready usesmedia.focalX/focalYto choose which part survives — but every image in the database sits at Payload's50/50default, so in practice every hero still cropped from dead centre and took faces first.A
beforeChangehook now blocks the publish and teaches:The escape hatch, and why it's needed
Payload writes
50/50for any upload whose point was never moved, so "untouched" is indistinguishable from "deliberately centred." Rather than deadlock an editor who genuinely wants a centre crop, articles gainfocalPointAcknowledged— ticked once, that article publishes.Strictly photo features only
Every other path returns before touching anything. Verified across the full matrix:
50/50, no ack50/50, ack ticked60/25Legacy import short-circuits first, so the 10K historical inserts are unaffected.
2. Photofeature credit matches the gallery treatment
Was
text-white/50, plain text, one photographer. Now sharesresolveCreditwith galleries: same precedence (explicit credit → media photographer → write-in), several photographers on one line, staff names linked to their profiles.Fixed white, not sampled — the hero always carries a dark bottom gradient, so there's nothing black would ever suit. No luminance sampling here.
3. Named versions, and 1.1.0
lib/version.tsderives a name from the major version and holds it for the whole major line —1.0.0,1.1.0and1.14.3are all "Indigo". Naming the next major is a one-line edit. Renders in the footer asv1.1.0 "Indigo".4. Staff profiles were slow and felt unclickable — two independent causes
Cause one, the query.
getPhotoArticleMappaginated every published article, 50 at a time, selecting the full lexicalcontentbody, looping until every portfolio photo resolved. For prolific photographers many photos were never used inline, so it walked the entirearticlestable — thousands of rows once the legacy archive counts — on every render. Exactly the top 5–6 by photo count.Featured images are an indexed relationship, so they're now resolved in one
featuredImage: { in: [...] }query that never readscontent. Only genuinely inline-only photos fall through to the scan, which drops todepth: 0and stops after 1,000 articles — a photo older than that renders without a link rather than holding up the page.Cause two, the images. The grid sent legacy-archive photos through Next's image optimizer. Those resolve to
/archive/*, served by a different upstream that the optimizer's loopback fetch can't reach, so the requests hang. A prolific photographer's grid is mostly archive photos — which is what made the page feel dead.RichTextParseralready guards inline uploads this way; the grid now does the same.Note
Cause two can't be reproduced locally — the dev database has no legacy rows (
source_url = 0). It's an objective code defect: the same class of media handled in one component and not the other.5. Migration
articles.focal_point_acknowledged+ its_articles_vshadow, registered in both paths perdocs/migrations.md— TS migration inmigrations/index.ts, equivalent SQL plus a batch-30 tracking row inrun_deploy_sql_migrations.sh. Defaultedfalse, so no backfill.Applied cleanly via the TS path on a fresh database (
pnpm db:migrate-test), and the production SQL path was run twice against a populated one to confirm idempotency.Checks
pnpm typecheckclean.pnpm lint0 errors (53 pre-existingmigrations/warnings, unchanged). All five items verified end to end against a running dev server.🤖 Generated with Claude Code