Skip to content

fix(core): normalize media in repeater image sub-fields - #2251

Open
dchaudhari7177 wants to merge 3 commits into
emdash-cms:mainfrom
dchaudhari7177:fix/repeater-media-normalize
Open

fix(core): normalize media in repeater image sub-fields#2251
dchaudhari7177 wants to merge 3 commits into
emdash-cms:mainfrom
dchaudhari7177:fix/repeater-media-normalize

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

What does this PR do?

normalizeMediaFields only visited fields whose top-level type is image/file, so a bare media id posted into a repeater image sub-field was stored verbatim and the admin rendered "Image not found". This walks the image sub-fields of repeater fields and normalizes their values the same way (via normalizeMediaValue), so a repeater sub-field gets the same treatment as a top-level image field. Top-level fields and non-repeater collections are unaffected; failures for a single sub-field are swallowed (as for top-level fields) so a save is never blocked.

Closes #2231

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
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool:

Screenshots / test output

Added tests/integration/content/repeater-media-normalize.test.ts: it posts the same bare media id into a top-level image field and a repeater image sub-field and asserts the repeater value normalizes identically to the top-level field (so it's harness-independent). It fails on main (the sub-field stays a raw id string) and passes here. The content integration suite + media-usage-content-fields stay green (55 tests); oxlint is clean on the changed files.

I ran targeted tests and oxlint/prettier on the changed files rather than the full pnpm typecheck/pnpm lint/pnpm test (which build the whole monorepo); happy to run those if you'd like.

Copilot AI review requested due to automatic review settings July 27, 2026 07:00
@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1d95270

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

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@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/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@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

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added area/core size/M review/awaiting-author Reviewed; waiting on the author to respond labels Jul 27, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

npm i https://pkg.pr.new/emdash@2251

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-verification

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 1d95270

@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 PR fixes the right bug in the right place: normalizeMediaFields only normalized top-level image/file fields, so a bare media ID posted into a repeater image sub-field was stored verbatim. The change walks repeater items and runs the same normalizeMediaValue helper on image sub-fields, with per-sub-field failures swallowed just like top-level failures. That matches the existing media-usage extraction code, which already knows how to find media IDs inside repeater image sub-fields.

I checked the changed runtime code and the new integration test, traced call-sites for normalizeMediaFields (used on both create and update paths), confirmed RepeaterSubField only supports image media sub-fields (so filtering sub.type === "image" is consistent and not an accidental omission of file), and verified there are no SQL, auth, locale-table, or logged-out hot-path changes.

No logic bugs or regressions. Two comments are PR-style narrative rather than evergreen code explanation; I'd tighten/delete them before merge.

Comment on lines +3666 to +3668
// (a bare media id posted inside a repeater item would otherwise be stored verbatim and
// render as "Image not found" in the admin).
const repeaterFields = collectionInfo.fields.filter(

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.

[suggestion] This three-line comment is PR narrative/justification: it explains the bug symptom and why the code exists, rather than a non-obvious invariant a future reader would get wrong. Per AGENTS.md, comments should be evergreen and not summarize the change or reference admin-only symptoms.

Suggested change
// (a bare media id posted inside a repeater item would otherwise be stored verbatim and
// render as "Image not found" in the admin).
const repeaterFields = collectionInfo.fields.filter(
// Repeater sub-fields are stored inside JSON items, so image sub-fields need
// the same MediaValue normalization as top-level image/file fields.

Alternatively, delete the comment entirely — the loop body is clear enough on its own.

Comment on lines +74 to +76
// Before the fix the bare id string was stored verbatim inside the repeater item, so the
// admin rendered "Image not found". It must now be normalized exactly like the top-level
// `hero` field (a MediaValue object, not the raw id string).

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.

[suggestion] This comment is change narrative (Before the fix..., It must now be normalized...), which belongs in the PR description/commit message, not in a test file. Per AGENTS.md, comments should not reference bugs, fixes, or PR context. The assertions below already make the expected behavior clear.

Suggested change
// Before the fix the bare id string was stored verbatim inside the repeater item, so the
// admin rendered "Image not found". It must now be normalized exactly like the top-level
// `hero` field (a MediaValue object, not the raw id string).
expect(typeof subImage).toBe("object");

(i.e., remove the three-line comment and keep the assertions.)

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 27, 2026
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 28, 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 still the right fix in the right place: normalizeMediaFields now walks repeater image sub-fields and applies the same normalizeMediaValue treatment as top-level image/file fields, which matches how media-usage extraction already discovers nested repeater media IDs. The create/update call sites are unchanged, and the new integration test compares the repeater sub-field output to the top-level field output so it doesn't overfit on a concrete object shape.

Since the prior review, nothing in the runtime code or test appears to have changed. The only priorfeedback still open is the two PR-style narrative comments flagged last time:

  • packages/core/src/emdash-runtime.ts still has a three-line comment that narrates the pre-fix bug ("a bare media id posted inside a repeater item would otherwise be stored verbatim and render as 'Image not found'"). The first line is legitimate intent; the rest is PR context.
  • The new test still has a comment that explains the pre-fix behavior rather than trusting its name and assertions.

No logic bugs, security issues, or AGENTS.md violations beyond comment discipline. I'd still tighten/delete those two comments before merge.

Comment on lines +3666 to +3668
// Repeater fields can contain image sub-fields, whose values need the same normalization
// (a bare media id posted inside a repeater item would otherwise be stored verbatim and
// render as "Image not found" in the admin).

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.

[suggestion] This comment explains the non-obvious intent on line 3666, but then narrates the PR bug history ("would otherwise be stored verbatim and render as 'Image not found' in the admin"). Per AGENTS.md commentary discipline, comments should be evergreen and addressed to future readers, not describe the pre-fix behavior.

Suggested change
// Repeater fields can contain image sub-fields, whose values need the same normalization
// (a bare media id posted inside a repeater item would otherwise be stored verbatim and
// render as "Image not found" in the admin).
// Walk repeater fields so their image sub-fields get the same normalization as top-level fields.

Comment on lines +74 to +76
// Before the fix the bare id string was stored verbatim inside the repeater item, so the
// admin rendered "Image not found". It must now be normalized exactly like the top-level
// `hero` field (a MediaValue object, not the raw id string).

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.

[suggestion] This comment restates pre-fix behavior and the PR's motivation ("Before the fix...", "Image not found"). The test name and assertions already describe the observable behavior; the comment is PR narrative, not evergreen test documentation.

Suggested change
// Before the fix the bare id string was stored verbatim inside the repeater item, so the
// admin rendered "Image not found". It must now be normalized exactly like the top-level
// `hero` field (a MediaValue object, not the raw id string).
expect(typeof subImage).toBe("object");
expect(subImage).not.toBe(mediaId);
expect(subImage).toEqual(hero);

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 28, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/awaiting-author Reviewed; waiting on the author to respond size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

normalizeMediaFields does not recurse into repeater sub-fields — bare media ids stored verbatim, admin shows "Image not found"

3 participants