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) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7865-artifact-viewer-mobile-sheet.md
Normal file
7
.changeset/fn-7865-artifact-viewer-mobile-sheet.md
Normal file
@@ -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.
|
||||
@@ -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
|
||||
<!-- FNXC:ArtifactsGalleryDocs 2026-07-12-12:02: Artifact viewer docs distinguish desktop draggable/resizable FloatingWindow behavior from the mobile full-screen sheet so users do not expect to drag a small artifact popup on touch devices. -->
|
||||
- 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 "<query>".`
|
||||
- Error state: a failed artifact list request uses the shared `Failed to load artifacts: <error>` panel with a **Retry** action that re-runs the artifact fetch
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user