Skip to content

feat(web-ui): open image attachments in a gallery viewer - #964

Open
time-attack wants to merge 1 commit into
mainfrom
multi-image-lightbox-viewer
Open

feat(web-ui): open image attachments in a gallery viewer#964
time-attack wants to merge 1 commit into
mainfrom
multi-image-lightbox-viewer

Conversation

@time-attack

@time-attack time-attack commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

What

Clicking an image attachment in a chat message used to open the raw artifact URL in a new tab, which is a dead end when someone uploads several pictures at once. This adds a proper gallery viewer to the web UI.

  • Clicking any image in a message opens a full-screen modal viewer scoped to that message.
  • Prev/next arrow buttons, /, Home/End, and touch swipe (on the image stage only) move between images; navigation wraps.
  • A filmstrip of thumbnails at the bottom jumps directly to an image; the current one is highlighted and scrolled into view.
  • Header shows the file name, size, an n / m counter, a zoom toggle, Download, Open original (when the image is a stored artifact), and Close.
  • Click the image (or the zoom button) to view at natural size, centred on the point you clicked; the stage scrolls. Zoom is disabled when the image already fits.
  • Esc, the Close button, or clicking the empty backdrop closes and returns focus to the tile that opened the viewer. Escape is trapped at the dialog so it no longer reaches the page-level handlers (split-pane un-maximise, sidebar selection clear).
  • A visually hidden role="status" announces "Image 2 of 3: name.png" on every navigation; all controls are labelled and keyboard reachable.
  • Composer attachment strip shows a thumbnail for each image attachment; clicking it opens the same viewer over the pending uploads so you can check them before sending.
  • Messages with more than one image now render as square cover tiles instead of a row of variably sized previews.

Implementation: one new module plugins/web-ui/src/lightbox.ts (native <dialog> + showModal(), single document-level host like the search palette), chat.ts routes both user attachments and delivered files through one messageFiles helper that builds the gallery, and the duplicated data-URL construction moved to inlineImageSrc in ui.ts.

Behaviour change to note: transcript image tiles are now <button>s instead of <a target="_blank">, so cmd/middle-click to a new tab no longer works on the tile itself. "Open original" inside the viewer covers that case.

Demo

Screenshots from the dev instance (Firefox-equivalent Chromium via Aside, real Pi turn, local Postgres):

Composer strip with thumbnails Three-image message as square tiles
composer transcript
Viewer, image 2 of 3 Zoomed to actual size
viewer zoomed

Verification

  • plugins/web-ui: typecheck (all three tsconfigs), eslint, prettier clean.
  • New test/lightbox.test.ts (jsdom via vite ssrLoadModule): open state and links, arrow-key wrap, Home/End, arrow buttons and filmstrip, swipe vs tap vs zoomed pan, single-image mode, Escape containment and focus restore, backdrop vs image click, index clamping. renderable-image-source.test.ts updated for the new chip structure.
  • Live QA on a dev-instance (--no-slack): pasted three PNGs, checked composer thumbnails and preview, sent, verified tiles, viewer navigation, zoom focus point, Escape not reaching document listeners, and the reload-from-history path where tiles and links come from /api/files/:id/content.
  • Two independent review passes (correctness; UX/a11y). Findings fixed in this PR: swipe bound to the whole dialog (now stage-only, disabled while zoomed), Escape leaking to document handlers (now via trapDialogFocus), long filenames blowing out the grid on narrow viewports (grid-template-columns: minmax(0, 1fr)), pointer-only zoom (header zoom button), stale zoomable state on resize, live-region wording, 44px header tap targets under 640px, color-scheme: dark scrollbars, transparent tile background.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Clicking an image in a chat message used to open the raw artifact URL
in a new tab, which is a dead end when a message carries several
pictures. Images now open in a full-screen modal viewer scoped to the
message: prev/next arrows, arrow-key, Home/End and swipe navigation, a
filmstrip of thumbnails, an n / m counter, download and open-original
links, click-to-zoom to natural size, and Escape or backdrop click to
close with focus returned to the tile that opened it.

The composer attachment strip shows a thumbnail for each image
attachment and opens the same viewer so uploads can be checked before
sending. Messages with more than one image render as square cover
tiles instead of a row of variably sized previews.
@time-attack
time-attack force-pushed the multi-image-lightbox-viewer branch from 935502b to 3f058d9 Compare September 7, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant