Repository navigation
feat(web): upload a broadcast's social image instead of pasting a URL - #175
Conversation
The Web page dialog of a broadcast now has a Social image control that reuses the email image picker (Upload, Your Media, Unsplash) in place of the "Social image URL" field. An uploaded MediaLit image is sealed on save, added to the media library and tracked with a new BROADCAST_WEB_PAGE media reference, so it can't be deleted from the library while the page uses it. Replacing or removing the image releases the reference but keeps the media; deleting the broadcast releases it and cleans up orphaned media. Other URLs, including ones sent through REST or MCP, are stored as they are. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cff8e6b03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| alt?: string; | ||
| resource: MediaReferenceResource; | ||
| }): Promise<string | null> { | ||
| const mediaLitId = url ? extractMediaLitIdFromUrl(url) : undefined; |
There was a problem hiding this comment.
Restrict sealing to MediaLit URLs
When REST/MCP supplies a non-MediaLit URL whose path ends in /<segment>/main.<ext> (for example, https://cdn.example.com/assets/main.jpg), the extractor treats the penultimate path segment as a MediaLit ID, so this call tries to seal assets instead of retaining the external URL. The update fails if MediaLit has no such ID, breaking compatibility for those external URLs; only seal URLs recognized as MediaLit uploads.
Useful? React with 👍 / 👎.
| } | ||
| if (input.ogImage !== undefined) values.webOgImage = input.ogImage || null; | ||
| if (input.ogImage !== undefined) { | ||
| values.webOgImage = await syncImageMediaReference({ |
There was a problem hiding this comment.
Apply the reference change only after the broadcast update succeeds
If another broadcast claims the requested slug after slugTaken passes, a request that also changes ogImage has already replaced this reference before the sequence update fails with slug_taken. The database still serves the old webOgImage, but its media is now unreferenced and can be deleted while the new upload is left unreferenced. Keep the reference replacement atomic with the sequence update, or defer it until that update succeeds.
Useful? React with 👍 / 👎.
- Only treat `/i/<id>/main.<ext>` and `/p/<id>/main.<ext>` URLs as MediaLit
uploads, so an external image that merely looks like one is kept as is.
- Seal first, but replace the media reference only after the broadcast is
saved, so a failed update (for example a slug race) keeps the previous
image referenced.
- Back the MCP server and parity tests with a real PGlite db: Better Auth
queries the db at import time, and a `{}` stub caused unhandled errors
that failed CI.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The test enrols at a fixed 2026-11-02T09:00Z, but seeds stamp `updatedAt` with the real clock. Once the real date passed that moment the sequence looked edited after enrolment and the early-send check stayed silent. Pin the timestamps before the enrolment. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Running parity.test.ts with a real PGlite next to platform-conformance.test.ts in one process (as `test:conformance` does) hangs. The parity test only lists tools, so give Better Auth an inert query builder instead. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts: # apps/api/src/automation/sequence-journey.test.ts # apps/api/src/mcp/parity.test.ts # apps/api/src/mcp/server.test.ts
What
The Web page dialog of a broadcast replaces the Social image URL field with a Social image control: a preview plus Upload image / Replace image and Remove. It opens the same picker as the email image block (Upload, Your Media, Unsplash), limited to JPEG, PNG and WebP. Uploads now show up in the Media library.
How it works
BROADCAST_WEB_PAGE(resource = the broadcast), so the image can't be deleted from the library while the page uses it. It is separate fromSEQUENCE_EMAILbecause saving the email body replaces that resource's references. No migration:resource_typeis plain text.deleteOrphanedMedia.BROADCAST_WEB_PAGEadded to the api-contract and MCP reference enums,ogImagedescription updated, Media page shows "Web page of broadcast …",apps/api/docs/media-library.mdupdated. No changeset: the touched packages are on the changeset ignore list.Testing
ioredisrefusing a connection because Redis isn't available in my sandbox./i/to/p/URL, library item "1 use", delete disabled with the new usage label, Remove, re-pick from Your Media (no duplicate), and delete broadcast (library emptied, no MediaLit delete errors).test:conformanceandcheck:drift.Notes
ogImagevalues that are plain URLs keep working and show in the preview.🤖 Generated with Claude Code