- MessageOverlays.tsx: revert to main (unrelated reactions-dialog fix
that snuck in; will follow up separately)
- composer.ts: rename MAX_IMAGES -> LEGACY_IMAGES_EMBED_MAX to flag it
as the legacy/gallery split threshold; replace stale TODO with a
defense-in-depth note matching applyGalleryCap's role
- drafts/state/api.ts: update import + restore-path usage to new name
- Composer.tsx: extract duplicated onImageAdd cap/toast/dispatch logic
into a shared useAddImagesWithCap hook, used by both ComposerPost
and ComposerFooter
- MediaPreview.tsx: replace filter().slice(4).map() with a single-pass
loop that stops at 4 tiles instead of walking a 10-image gallery
PR #10490 removed these as DynamicAppIcon.IconName casts because
@typescript-eslint/no-unnecessary-type-assertion flags them; my
review-feedback commit re-added them by mistake.
- drafts/state/api.ts: restore path now picks images vs gallery
variant by count instead of trusting the server slot, mirroring
imagesToMediaVariant. Save path was already correct in prior commits
- Composer.tsx: add applyGalleryCap helper and wire it into both
onImageAdd callsites (covers picker, paste, camera, drag-drop) so
the user gets a toast when adds would exceed MAX_GALLERY_IMAGES.
Reducer-level logger.warn remains as defense in depth
- ImageLayoutGrid/Item: propagate isWithinQuote into GalleryItem so
hideBadges also honors the explicit prop, matching the layout fix
- MessageOverlays: keep a snapshot of the last-known reactions message
so the bottom sheet plays its close animation through when the
underlying message is deleted; live updates while open are unchanged
- Composer.tsx: thread gallery through reply pills (hasMedia),
post:create/draft:save analytics, missingAltError, and publish-fail
logger annotation
- composer/state/composer.ts: surface a logger.warn when
embed_add_images would exceed MAX_GALLERY_IMAGES (hard slice
preserved); ascii-only comments
- maybeApplyGalleryOffsetStyles.ts: scope PostGalleryEmbedEnable gate
to image-embed branches so gallery posts always get no-content offsets
- MessageOverlays.tsx: store reactionsTargetId instead of a frozen
message snapshot; look up the live message each render. Memoize
reportSubject on reportTarget+convoId so ReportDialog's parseReportSubject
memo survives convo updates. Replace open-after-mount boolean with a
ref keyed to id so reopening the same message reliably re-triggers
- ImageEmbed.tsx + ImageLayoutGrid.tsx: thread isWithinQuote through
the 2-4 image fallthrough; explicit prop overrides the viewContext
heuristic
- parseReportSubject.ts: flip the image attribute true for gallery
embeds (top-level and post_with_media), matching the existing images
branches
- AppIconSettings/index.tsx: restore the IconName casts the previous
change dropped; IconName is still a literal union
- composer/photos/Gallery.tsx: wrap to a 4-column grid for n>4 so
10-image previews don't crush remove/alt-text controls
- lib/api/index.ts: normalize gallery upload log nouns to image/images
B1: `createComposerState` always typed the initial media as `ImagesMedia`
regardless of `initImageUris.length`. With the picker cap raised to 10 in
ComposerPrompt + the URL intent handler not capping at all, the FAB and
deep-link flows could land the composer with 5-10 images in an `images`
embed, which `app.bsky.embed.images` rejects (`maxLength: 4`) on submit.
Extract `imagesToMediaVariant` and reuse from `createComposerState`,
`embed_add_images`, and `embed_remove_image` so the variant decision
("<=4 = images, >4 = gallery, cap at 10") lives in one place. The
remove-image demote branch collapses into the helper too — same effect,
less code.
W1: `maybeApplyGalleryOffsetStyles` only short-circuited single-image
galleries, applying offset for 0-item galleries when nothing renders
below. Loosened to `<= 1` (covers both 0 and 1 with the same intent).
Bumps @atproto/api to 0.20.9, which ships the generated
`AppBskyEmbedGallery` namespace and `DraftPost.embedGallery`. Swaps every
`#/lib/api/gallery-embed-shim` import to `@atproto/api`, deletes the
shim, drops the local `DraftPostWithGallery` widen-types, and adds the
narrowing calls (`isViewImage`, `isDraftEmbedImage`) the real lexicon
unions require.
The merge also auto-resolves an inline image-restore block in
`drafts/state/api.ts` against main's typing tweak — main switched to
`satisfies ComposerImage` and `NonNullable<typeof img>`, which here move
into the existing `restoreDraftImages` helper that's now shared by both
the images and gallery restore paths.
Renames `GRID_TO_CAROUSEL_THRESHOLD` -> `MAX_GRID_IMAGES` and
`carouselEnabled` -> `useExpandedLayout` in ImageEmbed so the render-path
decision reads as a count threshold rather than naming the component
that happens to live downstream today.
Also slices the gallery branch of MediaPreview to the first 4 items so a
10-image gallery doesn't overflow the inline notification/DM row.
Wires the new gallery embed (atproto PR #4827) through the display and
compose pipelines while the lexicon is unpublished. Gallery posts render
via the existing carousel for >4 items and grid for <=4. Compose now
allows up to 10 images and auto-promotes images -> gallery above 4
(demotes back when count drops to <=4 so old clients still see legacy
embeds when possible). Drafts persist as the new `embedGallery` field.
Local shim types in `src/lib/api/gallery-embed-shim*.ts` mirror the
lexicon shape; delete both files once @atproto/api ships the generated
types.