Skip to content

feat(media): add image focal points - #2624

Open
khoinguyenpham04 wants to merge 3 commits into
feat/media-folders-uifrom
feature/media-focal-point-folders-stack
Open

feat(media): add image focal points#2624
khoinguyenpham04 wants to merge 3 commits into
feat/media-folders-uifrom
feature/media-focal-point-folders-stack

Conversation

@khoinguyenpham04

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds focal points for local images. Editors can set the important part of an image from the Media Details dialog, compare square, landscape, and portrait crops, and keep that subject visible in cover-cropped thumbnails, image fields, galleries, and EmDashImage.

The dialog uses native Kumo segmented tabs for Details and Edit image. Folder Location remains under Details, the crop previews sit beside the editor on desktop, and the dialog keeps a stable height while switching tabs.

This is PR 4/4 in the native media stack and is stacked directly on #2586:

  1. feat(media): add numbered library pagination #2582 — numbered Media Library pagination
  2. feat(media): add flat folder API foundation #2584 — flat folder API foundation
  3. feat(media): add flat folder UI #2586 — flat folder admin UI
  4. This PR — focal points

Related discussion: #990

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes for the affected admin and core packages
  • pnpm lint passes with 0 type-aware diagnostics
  • Targeted tests pass for the affected admin, API, database, client, migration, snapshot, and rendering behavior
  • pnpm format has been run and the full formatting check passes
  • I have added/updated tests for my changes
  • User-visible strings in the admin UI are wrapped for translation. No messages.po files are included.
  • I have added and reviewed the user-facing changeset
  • New features link to an approved Discussion: Make media handling a first-class editing workflow #990

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5), reviewed with GPT-5.6 Terra xhigh

Screenshots / test output

  • Type-aware lint: 0 diagnostics.
  • Admin focused browser suite: 131 tests passed.
  • Core focused suite: 88 tests passed.
  • Focused migration and concurrent-migrator suite: 31 tests passed.
  • Admin and core typechecks passed.
  • Full formatting check passed.
  • Browser-verified Details/Edit image switching, focal click and keyboard editing, folder Location preservation, stable dialog height, dark mode, 390px layout, and Arabic RTL.
  • Persistent adversarial review converged with no findings.

PostgreSQL and D1 were not available locally; SQLite and the existing migration-race fixture passed.

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 1,256 lines across 44 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 95bc8b1 Aug 26 2026, 05:05 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feature-media-focal-point-folders-stack.try.emdashcms.com, https://feature-media-focal-point-folders-stack-emdash-playground.emdash-cms.workers.dev (commit 95bc8b1)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://fc8fa10b.try.emdashcms.com, https://fc8fa10b-emdash-playground.emdash-cms.workers.dev 95bc8b1 2026-08-26T17:15:12.901Z Visit the dashboard ↗
  • Build: Failed ❌

View logs ↗
6095569 2026-08-26T15:21:09.833Z View logs ↗
  • Build: Failed ❌

View logs ↗
10049f2 2026-08-26T14:52:04.392Z View logs ↗
  • Build: Failed ❌

View logs ↗
ef67627 2026-08-26T14:16:01.158Z View logs ↗
  • Build: Failed ❌

View logs ↗
6f4c4f1 2026-08-26T09:59:30.845Z View logs ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://62c88fee.try.emdashcms.com, https://62c88fee-emdash-playground.emdash-cms.workers.dev 91e344d 2026-08-25T15:44:51.729Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://78f6536d.try.emdashcms.com, https://78f6536d-emdash-playground.emdash-cms.workers.dev 650077a 2026-08-24T14:22:47.434Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://7e9673f7.try.emdashcms.com, https://7e9673f7-emdash-playground.emdash-cms.workers.dev 94bf5e4 2026-08-24T13:32:22.373Z Visit the dashboard ↗

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 95bc8b1 Aug 26 2026, 05:09 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 95bc8b1 Aug 26 2026, 05:10 PM

@changeset-bot

changeset-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 95bc8b1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Minor
@emdash-cms/admin Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Major
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/admin@2624

@emdash-cms/auth

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth@2624

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth-atproto@2624

@emdash-cms/blocks

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/blocks@2624

@emdash-cms/cloudflare

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/cloudflare@2624

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/contentful-to-portable-text@2624

emdash

npm i https://pkg.pr.new/emdash-cms/emdash@2624

create-emdash

npm i https://pkg.pr.new/emdash-cms/emdash/create-emdash@2624

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/gutenberg-to-portable-text@2624

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@2624

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@2624

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@2624

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@2624

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-moderation@2624

@emdash-cms/registry-verification

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-verification@2624

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@2624

@emdash-cms/x402

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/x402@2624

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-ai-moderation@2624

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-atproto@2624

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-audit-log@2624

@emdash-cms/plugin-color

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-color@2624

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-embeds@2624

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@2624

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-forms@2624

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-webhook-notifier@2624

commit: 95bc8b1

@github-actions

Copy link
Copy Markdown
Contributor

@khoinguyenpham04
khoinguyenpham04 marked this pull request as ready for review August 25, 2026 10:38
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Aug 25, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-focal-point-folders-stack branch from 650077a to 686f750 Compare August 25, 2026 14:24
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-focal-point-folders-stack branch 2 times, most recently from c885c58 to 91e344d Compare August 25, 2026 14:45
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 26, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right change for the approved discussion: it adds image focal points end-to-end (DB column, API validation, admin editor with accessible pointer/keyboard input, and rendering via object-position) while keeping changes additive and consistent with EmDash conventions. I read the diff, AGENTS.md, the touched source files, tests, route, schema, and migration. The implementation is mostly clean — validation is thorough, the migration handles concurrent column-add races, the UI uses Lingui/Kumo and logical Tailwind, and render paths rely on stored snapshots so no extra logged-out queries are introduced.

The only substantive issue is a public-client type leak: EmDashClient.mediaSetFolder now types the PUT /media/:id response as MediaFolderAssignment, which had focal fields added to it even though that method is only about folder assignment and the endpoint actually returns the full MediaItem. That interface is misleading and should be narrowed back to folder info or replaced with MediaItem.

No security, data-integrity, localization, or query-count regressions identified.

Comment on lines 206 to 212
/** Result of assigning local media to a folder or the Main library */
export interface MediaFolderAssignment {
id: string;
focalX: number | null;
focalY: number | null;
folderId: string | null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] The MediaFolderAssignment interface is described as the “Result of assigning local media to a folder or the Main library,” but it now carries focalX/focalY. The underlying mediaSetFolder method (line 910) only sends { folderId } to PUT /_emdash/api/media/:id, and that endpoint returns the full MediaItem (which already has focalX/focalY). Adding focal-point fields to a narrow folder-assignment type is a leaky, confusing public API contract.

Either make mediaSetFolder return Promise<MediaItem> and keep MediaFolderAssignment as { id; folderId }, or remove MediaFolderAssignment entirely if nothing else uses it.

Suggested change
/** Result of assigning local media to a folder or the Main library */
export interface MediaFolderAssignment {
id: string;
focalX: number | null;
focalY: number | null;
folderId: string | null;
}
/** Result of assigning local media to a folder or the Main library */
export interface MediaFolderAssignment {
id: string;
folderId: string | null;
}

and update the method signature and request generic to use MediaItem:

Suggested change
/** Result of assigning local media to a folder or the Main library */
export interface MediaFolderAssignment {
id: string;
focalX: number | null;
focalY: number | null;
folderId: string | null;
}
/** Assign media to a folder, or return it to the Main library */
async mediaSetFolder(id: string, folderId: string | null): Promise<MediaItem> {
const data = await this.request<{ item: MediaItem }>(
"PUT",
`/media/${encodeURIComponent(id)}`,
{ folderId },
);
return data.item;
}

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 26, 2026
@github-actions github-actions Bot added the review/awaiting-author Reviewed; waiting on the author to respond label Aug 26, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/needs-review No maintainer or bot review yet review/awaiting-author Reviewed; waiting on the author to respond labels Aug 26, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-focal-point-folders-stack branch from ef67627 to 10049f2 Compare August 26, 2026 14:34
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-focal-point-folders-stack branch 2 times, most recently from 6095569 to 8debaa8 Compare August 26, 2026 16:33

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good. In future I'd like to think about how we could integrate this with image providers so that cropping works, buit for now this is good.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 26, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-focal-point-folders-stack branch 2 times, most recently from 8f8ad7e to d2a27e6 Compare August 26, 2026 16:51
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Aug 26, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-focal-point-folders-stack branch from d2a27e6 to 95bc8b1 Compare August 26, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants