Skip to content

feat: v1.1.0 "Indigo" — focal point enforcement, named versions, staff perf - #157

Merged
RonanHevenor merged 1 commit into
mainfrom
feat/v1.1.0-indigo
Sep 9, 2026
Merged

RonanHevenor merged 1 commit into
mainfrom
feat/v1.1.0-indigo

Conversation

@RonanHevenor

Copy link
Copy Markdown
Member

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.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 teaches:

This photo feature can't be published yet: its lead image has no focal point.

The photo feature hero crops to fill the whole screen. Without a focal point it crops from the middle, which is what cuts people's heads off on tall and narrow screens.

To fix it: open Media, find "", drag the focal-point marker onto the subject's face, and save. Then publish this article again.

The escape hatch, and why it's needed

Payload writes 50/50 for 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 gain focalPointAcknowledged — ticked once, that article publishes.

Strictly photo features only

Every other path returns before touching anything. Verified across the full matrix:

case result
photo feature, focal 50/50, no ack BLOCKED
photo feature, focal 50/50, ack ticked allowed
photo feature, focal 60/25 allowed
normal article allowed — untouched
photo feature, draft save allowed — never blocked

Legacy 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 shares resolveCredit with 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.ts derives a name from the major version and holds it for the 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. Renders in the footer as v1.1.0 "Indigo".

4. Staff profiles were slow and felt unclickable — two independent causes

Cause one, the query. 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 were never used inline, so it walked the entire articles table — 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 reads content. 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.

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. RichTextParser already 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_v shadow, registered in both paths per docs/migrations.md — TS migration in migrations/index.ts, equivalent SQL plus a batch-30 tracking row in run_deploy_sql_migrations.sh. Defaulted false, 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 typecheck clean. pnpm lint 0 errors (53 pre-existing migrations/ warnings, unchanged). All five items verified end to end against a running dev server.

🤖 Generated with Claude Code

…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>
@RonanHevenor
RonanHevenor merged commit 369ab71 into main Sep 9, 2026
7 checks passed
@RonanHevenor
RonanHevenor deleted the feat/v1.1.0-indigo branch September 9, 2026 22:21
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.

1 participant