Polish file viewer UX review punchlist (PAP-1962)

Addresses UX review feedback on PAP-1963:

- Fix denial-body leak for too_large/size codes (describeDenial no longer
  echoes fallback for known codes; the call site passes empty fallback so
  unknown codes show a generic message).
- Sticky line-number gutter with solid code-bg fill; highlight border
  moves from absolute-positioned sibling onto the sticky gutter so the
  amber marker stays pinned when users scroll long lines horizontally.
- Swap FileSearch copy-link icon for Link2.
- WorkspaceFileLink now sets a real deep-link href so modifier-click
  opens the file viewer in a new tab instead of scrolling to the top.
- Collapse duplicate title/description in the sheet header when the
  basename equals the full path (e.g. .env.production).
- Drop the duplicate title/helper block from OpenFilePrompt (sheet
  header already carries the title + description).
- Replace ArrowLeft with Cloud for the remote-workspace state.
- Retire arbitrary text-[12.5px]/[11px]/[0.78em] sizes in favor of text-xs.
- Unify 404 copy on "active workspace" everywhere.
- Raise chip-row contrast by using bg-muted instead of bg-background.

Also commits previously untracked visual-regression infra:
- Storybook stories for 12 file viewer states
- screenshot-file-viewer.mjs / screenshot-one.mjs
- describeDenial unit tests

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-06-09 21:35:48 +00:00
1 parent 4bad0cf17f
commit 9b58daba6e
8 files changed
+710 -39

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env node
// Screenshots every FileViewerSheet Storybook state at desktop + mobile viewports.
import fs from "node:fs/promises";
import path from "node:path";
import { chromium } from "@playwright/test";
const BASE = "http://localhost:6006/iframe.html";
const OUT_DIR = path.resolve(process.argv[2] || "/tmp/pap-1963");
const SHOTS = [
{ id: "components-file-viewer-sheet--text-content-with-highlighted-line", label: "content-highlighted-line" },
{ id: "components-file-viewer-sheet--text-content-long-path-truncation", label: "content-long-path" },
{ id: "components-file-viewer-sheet--open-file-prompt", label: "open-file-prompt" },
{ id: "components-file-viewer-sheet--loading-spinner", label: "loading-spinner" },
{ id: "components-file-viewer-sheet--error-not-found-with-fallback", label: "error-not-found" },
{ id: "components-file-viewer-sheet--error-no-workspace", label: "error-no-workspace" },
{ id: "components-file-viewer-sheet--error-outside-workspace", label: "error-outside-workspace" },
{ id: "components-file-viewer-sheet--error-denied-sensitive", label: "error-denied" },
{ id: "components-file-viewer-sheet--remote-workspace", label: "remote-workspace" },
{ id: "components-file-viewer-sheet--workspace-archived", label: "workspace-archived" },
{ id: "components-file-viewer-sheet--binary-unsupported", label: "binary-unsupported" },
{ id: "components-file-viewer-sheet--too-large-to-preview", label: "too-large" },
];
const VIEWPORTS = [
{ name: "desktop", width: 1440, height: 900 },
{ name: "mobile", width: 390, height: 844 },
];
async function run() {
await fs.mkdir(OUT_DIR, { recursive: true });
const browser = await chromium.launch({ headless: true });
try {
for (const vp of VIEWPORTS) {
const context = await browser.newContext({
viewport: { width: vp.width, height: vp.height },
deviceScaleFactor: 2,
});
const page = await context.newPage();
for (const shot of SHOTS) {
const url = `${BASE}?id=${shot.id}&viewMode=story&args=`;
console.log(`[${vp.name}] → ${shot.label}`);
await page.goto(url, { waitUntil: "domcontentloaded" });
// Give the sheet animation + potential network stubs ~1.5s to settle.
await page.waitForTimeout(shot.label === "loading-spinner" ? 900 : 1600);
const out = path.join(OUT_DIR, `${vp.name}-${shot.label}.png`);
await page.screenshot({ path: out, fullPage: false });
}
// Extra capture: mobile view story (its own viewport story) as a cross-check.
if (vp.name === "mobile") {
await page.goto(
`${BASE}?id=components-file-viewer-sheet--mobile-view&viewMode=story&args=`,
{ waitUntil: "domcontentloaded" },
);
await page.waitForTimeout(1600);
await page.screenshot({
path: path.join(OUT_DIR, `mobile-story-variant.png`),
fullPage: false,
});
}
await context.close();
}
} finally {
await browser.close();
}
console.log(`\nScreenshots written to ${OUT_DIR}`);
}
run().catch((error) => {
console.error(error);
process.exit(1);
});
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env node
// Screenshot a single story id at desktop 1440x900.
import fs from "node:fs/promises";
import path from "node:path";
import { chromium } from "@playwright/test";
const [, , storyId, out, widthArg, heightArg, waitArg] = process.argv;
const width = Number(widthArg) || 1440;
const height = Number(heightArg) || 900;
const waitMs = Number(waitArg) || 2500;
const url = `http://localhost:6006/iframe.html?id=${storyId}&viewMode=story`;
await fs.mkdir(path.dirname(path.resolve(out)), { recursive: true });
const browser = await chromium.launch({ headless: true });
try {
const ctx = await browser.newContext({
viewport: { width, height },
deviceScaleFactor: 2,
});
const page = await ctx.newPage();
await page.goto(url, { waitUntil: "networkidle" });
await page.waitForTimeout(waitMs);
await page.screenshot({ path: out, fullPage: false });
} finally {
await browser.close();
}
console.log(`Wrote ${out}`);