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:
gsxdsm
2026-07-12 12:10:38 -07:00
parent 95a808af6e
commit b8c18becd3
4 changed files with 100 additions and 3 deletions

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

View File

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

View File

@@ -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);
}

View File

@@ -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;/);
});
});