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