Files
multica/apps/mobile/lib
Naiyuan Qing 3e1a7e0404 feat(editor): MUL-5752 image preview prev/next navigation (#6424)
* feat(editor): page through an issue's / chat's images (MUL-5752)

Opening any image in an issue or a chat session now starts a sequence: the
viewer shows "3 / 7", chevrons and Left/Right walk to the neighbouring
images, and the ends disable rather than wrap.

Images only. PDFs, video, audio and text keep their single-file preview —
mixing kinds would mean defining load, keyboard and playback semantics for a
"next" that can land on a PDF page.

- packages/core/attachments/image-sequence.ts: the ordered sequence for a set
  of {content, attachments} blocks, plus the image test, the URL to attachment
  match and the standalone-attachment rule the renderers already applied
  separately. Built from data, not the DOM, because both the issue timeline
  and the chat list are virtualized. Shared with mobile.
- ImageSequenceProvider hosts one viewer per surface and freezes the sequence
  on open, so arriving comments cannot shift the index under the reader. An
  image the sequence does not know (a composer's in-flight upload) still
  opens on its own.
- A frame that fails to load is skipped in the direction of travel with a
  light toast, so a deleted attachment cannot trap the reader.
- Mobile keeps the semantics with its own interaction: the lightbox pages the
  same sequence by horizontal swipe and shows the same counter.

The re-sign hook moves out of attachment.tsx into hooks/use-inline-media-url
so the modal can upgrade an auth-gated URL for an image the reader navigated
to rather than clicked — without it, paging broke on Desktop and on
proxy-mode self-hosts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(mobile): call the chat image-sequence memo unconditionally (MUL-5752)

`imageBlocks` sat below the loading and empty-state early returns, so the
useMemo only ran on renders that got past both — a rules-of-hooks violation
that mobile CI caught. Moved above both returns; an empty `messages` just
yields an empty block list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(editor): flashless sequence navigation and preview header alignment

- Keep PreviewPanel mounted across navigation; swap the image only after
  the next frame decodes (useSettledImageURL), and prefetch both
  neighbours while the viewer is open
- Baseline-align filename/type in the header, keep ZoomControls mounted
  (disabled until measured), move the position counter to a bottom pill
- Disabled boundary arrows keep receiving pointer events so the zoom
  canvas grab cursor can't show over a dead control

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): only keep zoom controls mounted for sequence previews

Standalone previews restore the original gate (hidden until measured,
hidden for content with no intrinsic size); the always-mounted variant
broke tests whose i18n mocks never expected the canvas strings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-08-05 15:54:52 +08:00
..