From b8c18becd38ae5a108de0ece0aeaad61179d7e87 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 12 Jul 2026 12:10:38 -0700 Subject: [PATCH] FN-7865: make artifact viewer popups full-screen sheets on mobile Fixes the artifact viewer FloatingWindow (image/video/PDF/document) so it opens as a full-screen sheet on mobile instead of a small draggable/resizable desktop-style window. - Add a mobile-breakpoint override for .artifacts-gallery-window that clamps the FloatingWindow to inset:0/100vw/100dvh with no border/radius/shadow - Hide the FloatingWindow resize handle and disable header drag cursor/touch-action on mobile so the sheet can't be dragged or resized like the desktop window - Add a CSS-contract regression test (ArtifactsGallery.css.test.ts) asserting the mobile sheet rules exist while desktop keeps the header drag affordance - Update dashboard-guide.md docs to describe desktop draggable/resizable behavior vs. mobile full-screen sheet behavior - Add a patch changeset for @runfusion/fusion documenting the fix Files changed: .changeset/fn-7865-artifact-viewer-mobile-sheet.md | 7 +++ docs/dashboard-guide.md | 5 +- .../dashboard/app/components/ArtifactsGallery.css | 28 +++++++++- .../__tests__/ArtifactsGallery.css.test.ts | 63 ++++++++++++++++++++++ 4 files changed, 100 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-7865 Fusion-Task-Lineage: 0b5dfaee-3f7b-4a2e-968c-324182ae7953 Co-authored-by: Fusion (runfusion.ai) --- .../fn-7865-artifact-viewer-mobile-sheet.md | 7 +++ docs/dashboard-guide.md | 5 +- .../app/components/ArtifactsGallery.css | 28 ++++++++- .../__tests__/ArtifactsGallery.css.test.ts | 63 +++++++++++++++++++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 .changeset/fn-7865-artifact-viewer-mobile-sheet.md create mode 100644 packages/dashboard/app/components/__tests__/ArtifactsGallery.css.test.ts diff --git a/.changeset/fn-7865-artifact-viewer-mobile-sheet.md b/.changeset/fn-7865-artifact-viewer-mobile-sheet.md new file mode 100644 index 0000000000..64699e7052 --- /dev/null +++ b/.changeset/fn-7865-artifact-viewer-mobile-sheet.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Make artifact preview popups full-screen sheets on mobile. +category: fix +dev: Adds mobile CSS and a CSS-contract regression test for artifact FloatingWindow viewers. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 8e9c514cc7..54a12c77dd 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -845,10 +845,11 @@ Features: - Video artifacts (agent-registered recordings, `path`-ingested MP4/WebM/MOV, and bridged video attachments) play with working seek because the media route serves HTTP byte ranges - HTML doc artifacts (`mimeType: text/html`) render as **live sandboxed previews** by default in the document viewer (scripts allowed, same-origin denied), with a Preview/Source toggle and the same Edit mode as other docs - **Edit any inline-content doc in place**: the document viewer's **Edit** button switches to an editor whose **Save** persists through `PATCH /api/artifacts/:id` and live-refreshes open galleries via the `artifact:updated` registry event; binary-backed documents stay read-only with a media link -- Every viewer (image/video lightbox, PDF viewer, document viewer) opens in a draggable, resizable floating window (drag by the viewer header, resize by any edge/corner; geometry persists per viewer kind); dismiss with the close button or Escape. Windows are non-blocking, so the gallery behind them stays interactive + +- Every viewer (image/video lightbox, PDF viewer, document viewer) opens in a draggable, resizable floating window on desktop/tablet (drag by the viewer header, resize by any edge/corner; geometry persists per viewer kind); dismiss with the close button or Escape. Windows are non-blocking, so the gallery behind them stays interactive - Read artifact metadata on cards, rows, and viewer footers: title, optional description, author ID, timestamp, size, and linked task ID when present - Use the task link on a card/row or viewer footer to jump back to the originating task when the artifact has a `taskId`; inside task detail, the **Artifacts** tab shows that task's documents and registered media artifacts together -- The gallery scales down at the mobile breakpoint (including landscape phones): category chips scroll horizontally, visual grids collapse to two columns, cards and rows go single-column, and viewer windows clamp to the viewport +- The gallery scales down at the mobile breakpoint (including landscape phones): category chips scroll horizontally, visual grids collapse to two columns, cards and rows go single-column, and viewer windows open as full-screen sheets without desktop resize handles or drag cursors - Loading state: the Artifacts tab shows `Loading artifacts…` while the first artifact list request is pending and no artifact results are loaded - Empty states: with no search query it shows `No artifacts yet.` plus the hint that artifacts are created by agents, users, and system tools; with a search query it shows `No artifacts match "".` - Error state: a failed artifact list request uses the shared `Failed to load artifacts: ` panel with a **Retry** action that re-runs the artifact fetch diff --git a/packages/dashboard/app/components/ArtifactsGallery.css b/packages/dashboard/app/components/ArtifactsGallery.css index 837ebd5b19..58d7f595cf 100644 --- a/packages/dashboard/app/components/ArtifactsGallery.css +++ b/packages/dashboard/app/components/ArtifactsGallery.css @@ -532,7 +532,33 @@ Viewers live inside the shared FloatingWindow (draggable by the viewer header, r width: 100%; } - /* FloatingWindow clamps viewer geometry to the viewport, so mobile only tightens the inner padding. */ + /* + FNXC:ArtifactsGallery 2026-07-12-12:02: + On mobile the artifact viewer popup must match chat and task-detail FloatingWindow sheets instead of remaining a small draggable desktop window that is hard to move on touch. Scope the geometry override to the artifact window so desktop keeps header drag and edge/corner resize behavior. + */ + .artifacts-gallery-window { + inset: 0 !important; + width: 100vw !important; + height: 100dvh !important; + min-width: 0 !important; + min-height: 0 !important; + max-width: 100vw !important; + max-height: 100dvh !important; + border: none; + border-radius: 0; + box-shadow: none; + } + + .artifacts-gallery-window .floating-window__resize-handle { + display: none; + } + + .artifacts-gallery-window .artifacts-gallery-viewer-header, + .artifacts-gallery-window .artifacts-gallery-viewer-header:active { + cursor: default; + touch-action: auto; + } + .artifacts-gallery-viewer { padding: var(--space-sm); } diff --git a/packages/dashboard/app/components/__tests__/ArtifactsGallery.css.test.ts b/packages/dashboard/app/components/__tests__/ArtifactsGallery.css.test.ts new file mode 100644 index 0000000000..0c3ba350da --- /dev/null +++ b/packages/dashboard/app/components/__tests__/ArtifactsGallery.css.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "vitest"; +import { loadAllAppCss, loadAllAppCssBaseOnly } from "../../test/cssFixture"; + +function extractMediaBlocks(css: string, query: string): string[] { + const blocks: string[] = []; + let cursor = 0; + while (cursor < css.length) { + const start = css.indexOf("@media", cursor); + if (start < 0) break; + const open = css.indexOf("{", start); + if (open < 0) break; + const mediaQuery = css.slice(start + "@media".length, open).trim(); + let depth = 1; + let i = open + 1; + while (i < css.length && depth > 0) { + if (css[i] === "{") depth += 1; + else if (css[i] === "}") depth -= 1; + i += 1; + } + if (mediaQuery.includes(query)) { + blocks.push(css.slice(open + 1, i - 1)); + } + cursor = i; + } + return blocks; +} + +function findRule(css: string, selector: string): string | undefined { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return css.match(new RegExp(`${escapedSelector}\\s*\\{[^}]*\\}`))?.[0]; +} + +describe("ArtifactsGallery mobile FloatingWindow sheet contract", () => { + it("FN-7865: makes artifact viewer windows full-screen sheets on mobile", () => { + const css = loadAllAppCss(); + const mobileCss = extractMediaBlocks(css, "(max-width: 768px)").join("\n"); + const windowRule = findRule(mobileCss, ".artifacts-gallery-window"); + const resizeHandleRule = findRule(mobileCss, ".artifacts-gallery-window .floating-window__resize-handle"); + const headerRule = findRule(mobileCss, ".artifacts-gallery-window .artifacts-gallery-viewer-header,\n .artifacts-gallery-window .artifacts-gallery-viewer-header:active"); + + expect(windowRule).toBeTruthy(); + expect(windowRule).toMatch(/inset:\s*0\s*!important;/); + expect(windowRule).toMatch(/width:\s*100vw\s*!important;/); + expect(windowRule).toMatch(/height:\s*100dvh\s*!important;/); + + expect(resizeHandleRule).toBeTruthy(); + expect(resizeHandleRule).toMatch(/display:\s*none;/); + + expect(headerRule).toBeTruthy(); + expect(headerRule).toMatch(/cursor:\s*default;/); + expect(headerRule).toMatch(/touch-action:\s*auto;/); + expect(headerRule).not.toMatch(/cursor:\s*grab(?:bing)?/); + expect(headerRule).not.toMatch(/touch-action:\s*none/); + }); + + it("preserves the desktop header drag affordance outside mobile media queries", () => { + const css = loadAllAppCssBaseOnly(); + const headerRule = findRule(css, ".artifacts-gallery-viewer-header"); + + expect(headerRule).toBeTruthy(); + expect(headerRule).toMatch(/cursor:\s*grab;/); + }); +});