Skip to content

feat(admin,core): image sub-fields in repeaters with media picker#1425

Merged
ascorbic merged 2 commits into
emdash-cms:mainfrom
swissky:feat/repeater-image-subfields
Jun 12, 2026
Merged

feat(admin,core): image sub-fields in repeaters with media picker#1425
ascorbic merged 2 commits into
emdash-cms:mainfrom
swissky:feat/repeater-image-subfields

Conversation

@swissky

@swissky swissky commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Repeater rows previously rendered every non-scalar sub-field as a plain text input, so common shapes like photo galleries had to be built from hand-pasted URLs (full context in #1424). This adds first-class image sub-fields:

  • RendererRepeaterField's sub-field switch gains case "image" and renders the same media-picker UI as top-level image fields (select / preview / change / remove), storing the same MediaValue shape; legacy string URLs still display via the existing backwards-compat path. New items initialize image sub-fields as null (instead of "").
  • Reuse without a cycleImageFieldRenderer (+ its ImageFieldValue shape) moved verbatim from ContentEditor.tsx into its own module; ContentEditor imports RepeaterField, so importing back from ContentEditor would have been circular. No behavior change to top-level image fields.
  • Schema builder — the sub-field type select offers Image (Lingui-wrapped like its siblings).
  • Core whitelistsREPEATER_SUB_FIELD_TYPES / RepeaterSubField.type and the API Zod enum gain image. The Zod enum also gains the previously missing url entry — the builder UI and REPEATER_SUB_FIELD_TYPES already offered/documented it, but createFieldBody rejected it.

No new value-validation path is needed: repeater item values go through the existing z.unknown() repeater handling in the zod generator, identical to before.

Closes #1424

Process note: Ideas Discussion opened at #1428 (background analysis in issue #1424). Scope-guard note: 542 lines are dominated by the verbatim move of ImageFieldRenderer out of ContentEditor.tsx (no behavior change) plus tests — the net new logic is ~60 lines.

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 (packages/admin + packages/core)
  • pnpm lint passes (lint:quick clean)
  • pnpm test passes (targeted: admin tests/components/RepeaterField.test.tsx 5/5 incl. 2 pre-existing datetime tests, FieldEditor.test.tsx 36/36, core field-validation-schema.test.ts 10/10; new tests red without the 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 (the new Image select label uses the Lingui t macro; no messages.po changes included per CONTRIBUTING)
  • I have added a changeset (@emdash-cms/admin minor, emdash minor)
  • New features link to an approved Discussion: Image sub-fields in repeater fields (media picker in repeater rows) #1428 (pending maintainer approval; background in issue Repeater fields: no image/media subfield support (builder and renderer) #1424)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Cursor + Fable 5 (reviewed and verified by a human before submission)

Screenshots / test output

❯ vitest run tests/components/RepeaterField.test.tsx   # without renderer change
  3 failed (image sub-field cases) | 2 passed (datetime)

❯ vitest run tests/components/RepeaterField.test.tsx tests/components/FieldEditor.test.tsx
  Tests  41 passed (41)

❯ vitest run tests/unit/api/field-validation-schema.test.ts   # core
  Tests  10 passed (10)   # 2 failed without the Zod enum change (image, url)

Repeater rows rendered non-scalar sub-fields as plain text inputs.
Adds case 'image' to the sub-field renderer (reusing ImageFieldRenderer,
extracted from ContentEditor to avoid a circular import), the builder
type option, and extends REPEATER_SUB_FIELD_TYPES + the API Zod enum
(which was also missing the documented 'url' type).

Closes emdash-cms#1424
@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d76c937

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

This PR includes changesets to release 14 packages
Name Type
@emdash-cms/admin Minor
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-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

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 542 lines across 9 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.

@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jun 12, 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 a well-scoped, clean implementation of image sub-fields inside repeaters.

Approach — The change solves the right problem (#1424): repeater rows previously forced every non-scalar sub-field through a plain text input, which made photo galleries painful. Extracting ImageFieldRenderer into its own module to avoid a circular import (ContentEditorRepeaterFieldImageFieldRenderer) is the correct architectural move, and the component is moved verbatim with no behavior change to top-level image fields.

What I checked

  • Diff + full context of all changed files (ContentEditor, RepeaterField, FieldEditor, ImageFieldRenderer, core schema/types, tests, changeset).
  • Traced ImageFieldRenderer extraction against the original inline code — verbatim.
  • Verified REPEATER_SUB_FIELD_TYPES, the Zod enum in createFieldBody, and the schema-builder select stay in sync and now correctly include both image and the previously missing url.
  • Checked AGENTS.md conventions: all new UI strings are Lingui-wrapped, Tailwind classes are RTL-safe (logical end-*, ms-*), no SQL or content-table changes, changeset is present and correctly versioned.
  • Confirmed repeater item values still flow through the existing z.unknown() path in the Zod generator — no new validation surface was needed.
  • Reviewed test coverage: targeted component tests exercise rendering the picker, showing an existing media-value preview, and initializing new items with null; core tests iterate all whitelisted sub-field types and reject unsupported ones.

Headline conclusion — No bugs, no regressions, no convention violations. The PR is ready to land.

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels Jun 12, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: d76c937

@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 Jun 12, 2026
@swissky

swissky commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up on the red Smoke Tests check: it fails in site-matrix-smoke ("all templates and playground build successfully"), which downloads seed images from Unsplash during the build. Every other suite passes on the same commit — Tests, Integration, Browser, all 8 E2E shards, Lint and Typecheck. This PR's core change is purely additive (adds "url" and "image" to the repeater sub-field enums; no template uses image sub-fields), so it can't affect a template build. Looks like a transient network flake on the image download — a re-run should clear it. I don't have rerun permissions here.

@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.

Thanks!

@ascorbic ascorbic merged commit 3e344af into emdash-cms:main Jun 12, 2026
68 of 69 checks passed
@emdashbot emdashbot Bot mentioned this pull request Jun 12, 2026
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.

Repeater fields: no image/media subfield support (builder and renderer)

2 participants