Publish every attached image, not just the first - #8
Open
pallaoro wants to merge 1 commit into
Open
Conversation
The composer takes `multiple` files, stores a media row per image and shows a thumbnail for each — then publishing sent `media[0]` and dropped the rest, on every platform, with no warning. Same class as the single-image drop, one level up. Each platform now carries the whole set, using the shape its own contract defines: X up to 4 media_media_ids — one staged upload per image LinkedIn up to 20 images[] of broker-staged descriptors Instagram 1-10 one image = a plain container, 2+ = a carousel Facebook — FACEBOOK_CREATE_MULTI_PHOTO_POST for 2+ photos TikTok 1-35 photo_images[], first image is the cover Bluesky up to 4 one blob upload per image into the embed The Instagram path also moves off two actions Composio marks deprecated (INSTAGRAM_CREATE_MEDIA_CONTAINER, INSTAGRAM_CREATE_POST) onto INSTAGRAM_POST_IG_USER_MEDIA / _PUBLISH — the carousel container has no deprecated publish partner, so the whole case had to move anyway. Attaching more images than a platform accepts fails that channel with the reason, rather than posting a truncated set: shipping fewer images than the user attached, silently, is the bug this path exists to close, and the per-channel delivery model means the other channels still go out. The composer warns while the images are still on screen and removable. The limits live in src/shared/platforms.ts because the server enforces the same table the composer warns on — two copies would drift, and the copy that drifts is the one that only fails at send time. Caps verified against Composio's live v3.1 tool schemas and the atproto app.bsky.embed.images lexicon, not recalled. Facebook is deliberately uncapped: photo_urls declares minItems and no maximum, so there is no stated number to enforce and Facebook's own rejection is the error we surface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
The composer takes
multiplefiles, stores amediarow per image and renders a thumbnail for each. Publishing then did this:Every platform got one image. The rest were dropped silently — no warning in the composer, no error on the post. Same class as the single-image drop fixed in #7, one level up.
What each platform gets now
Every cap below was read off the platform's own contract — Composio's live v3.1 tool schemas and the atproto lexicon — not recalled.
media_media_ids, one staged upload per imageTWITTER_CREATION_OF_A_POST— "Up to 4 Media IDs"images[]of broker-staged descriptorsLINKEDIN_CREATE_LINKED_IN_POST.images—maxItems: 20INSTAGRAM_CREATE_CAROUSEL_CONTAINER— 2–10 childrenFACEBOOK_CREATE_MULTI_PHOTO_POSTfor 2+photo_urlsdeclaresminItems, no maximumphoto_images[], first image is the coverTIKTOK_POST_PHOTO.photo_images—maxItems: 35app.bsky.embed.images.images—maxLength: 4Facebook is deliberately left uncapped: with no stated maximum there is no number to enforce, and Facebook's own rejection is the error we surface. That's the stance
bluesky.tsalready takes on text length.Over the cap fails the channel
Attach 6 images with X selected and X fails with "X / Twitter takes at most 4 images; this post has 6." — every other channel still goes out, because the per-channel delivery model exists for exactly this. Truncating to 4 would silently publish something the user did not write, which is the bug this whole path exists to close.
The composer surfaces the same message while the images are still on screen and removable, so it normally never reaches publish time.
Instagram moved off deprecated actions
Both actions this case called are marked deprecated in Composio's catalogue:
INSTAGRAM_CREATE_MEDIA_CONTAINER→INSTAGRAM_POST_IG_USER_MEDIAINSTAGRAM_CREATE_POST→INSTAGRAM_POST_IG_USER_MEDIA_PUBLISHThe carousel container has no deprecated publish partner, so the case had to move regardless — building the new path on a deprecated action would have been a wound.
Why
src/shared/platforms.tsThe server enforces the same table the composer warns on. Two copies drift, and the copy that drifts is the one that only fails at send time. The platform tables move there;
client/types.tsre-exports them, so no client component changed its imports.Verification
mediaLimitErrorexercised over 13 boundary cases (at cap, one over, zero images, uncapped platform, unknown platform); all correct.tsc --noEmit— no new errors (4 pre-existingD1Database/R2Bucketglobal-type errors remain, in lines this PR does not touch).vite buildandwrangler deploy --dry-runboth clean.Not verified: no end-to-end publish against a live social account — that needs connected accounts and posts to a real audience. The per-platform request shapes are grounded in the schemas, but the first real multi-image send on each platform is still the true test.
Left out, deliberately
PostPreviewtakesimageUrl={mediaUrls[0]}, so a 3-image post previews as 1. Worth its own PR — each platform renders a gallery differently./api/uploadrejects non-images.FACEBOOK_GET_USER_PAGES, used by the channel form, is also deprecated (→FACEBOOK_LIST_MANAGED_PAGES). Not on this PR's path.