fix(dashboard): make selection Add-comment trigger clickable and label read-only previews

The global .btn:active press-feedback transform (scale 0.97) overrode
the selection-comment trigger's positioning translate, teleporting the
fixed-position button mid-press so the click never landed and the
composer silently never opened. The :active state now restates the
translate alongside the scale on desktop and mobile offsets. Document
previews also gain a subtle Read-only badge explaining select-to-comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-10 16:08:35 -07:00
parent db247557b5
commit f628095205
12 changed files with 132 additions and 5 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix the Artifacts preview "Add comment" button doing nothing when clicked, and label the preview as read-only.
category: fix
dev: The global `.btn:active { transform: scale(0.97) }` press feedback replaced the selection-comment trigger's positioning translate while the mouse was held, moving the button out from under the cursor so `click` never fired. `.selection-comment-trigger:active` now restates the translate (desktop and mobile), covering DocumentsView and FileEditor surfaces. The Artifacts project-file preview header also gains a `documents.readOnly` badge.

View File

@@ -318,6 +318,19 @@ Artifacts controls are the first page content below the shared header, so add a
flex: 1; flex: 1;
} }
/*
FNXC:ArtifactsView 2026-07-10-16:10:
Subtle view-only indicator for the project-file preview header; muted so it informs without competing with the Plain/Markdown toggle. Shared markup serves desktop and mobile.
*/
.documents-readonly-badge {
flex-shrink: 0;
color: var(--text-muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
}
.documents-content-viewer-text { .documents-content-viewer-text {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);

View File

@@ -744,6 +744,16 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac
<div className="documents-content-viewer"> <div className="documents-content-viewer">
<div className="documents-content-header"> <div className="documents-content-header">
<p className="documents-file-path-header">{selectedFile.path}</p> <p className="documents-file-path-header">{selectedFile.path}</p>
{/*
FNXC:ArtifactsView 2026-07-10-16:10:
First-run review feedback: the Artifacts preview pane did not communicate whether documents are editable. This pane is view-only (there is no editor here — editing happens elsewhere, e.g. the workspace FileEditor), so a persistent Read-only badge states that explicitly on both desktop and mobile. Select-to-comment still works and is the intended interaction.
*/}
<span
className="documents-readonly-badge badge"
title={t("documents.readOnlyHint", "This preview is read-only. Select text to comment and send it to a new task.")}
>
{t("documents.readOnly", "Read-only")}
</span>
<button <button
className="btn btn-sm document-mode-toggle" className="btn btn-sm document-mode-toggle"
onClick={() => setRenderProjectMarkdown((prev) => !prev)} onClick={() => setRenderProjectMarkdown((prev) => !prev)}

View File

@@ -12,6 +12,24 @@
white-space: nowrap; white-space: nowrap;
} }
/*
FNXC:ArtifactsView 2026-07-10-16:05:
The "Add comment" trigger is positioned entirely by its transform translate. The global
`.btn:active { transform: scale(0.97) }` press feedback REPLACED that translate while the
mouse button was held, teleporting the trigger about half its width right and one spacing
unit down between mousedown and mouseup, so the browser never dispatched `click` on the
button and the comment composer silently never opened (user-reported no-op while viewing a
markdown file in the Artifacts view; reproduced with real mouse events — jsdom clicks
cannot catch this).
Every :active override here must restate the positioning translate alongside the press
feedback so the trigger stays under the cursor for the whole press. This one shared rule
covers all popover surfaces: DocumentsView project-file preview (plain and markdown) and
FileEditor (editor and preview), desktop and mobile.
*/
.selection-comment-trigger:active {
transform: translate(-50%, calc(-1 * var(--space-xl))) scale(0.97);
}
.selection-comment-panel { .selection-comment-panel {
width: min(var(--selection-comment-panel-width, calc(var(--space-2xl) * 12)), calc(100vw - (var(--space-lg) * 2))); width: min(var(--selection-comment-panel-width, calc(var(--space-2xl) * 12)), calc(100vw - (var(--space-lg) * 2)));
transform: translate(-50%, var(--space-xs)); transform: translate(-50%, var(--space-xs));
@@ -62,6 +80,16 @@
transform: translate(-50%, calc(-1 * var(--space-2xl))); transform: translate(-50%, calc(-1 * var(--space-2xl)));
} }
/*
FNXC:ArtifactsView 2026-07-10-16:05:
Mobile uses a taller lift, so its :active rule must restate the mobile translate too —
otherwise the desktop :active rule (space-xl) would snap the pressed trigger to the
desktop offset and reintroduce the missed-click no-op on touch/landscape-phone widths.
*/
.selection-comment-trigger:active {
transform: translate(-50%, calc(-1 * var(--space-2xl))) scale(0.97);
}
.selection-comment-actions { .selection-comment-actions {
flex-direction: column-reverse; flex-direction: column-reverse;
} }

View File

@@ -567,6 +567,24 @@ describe("DocumentsView", () => {
expect(await screen.findByText(/Hello docs/)).toBeInTheDocument(); expect(await screen.findByText(/Hello docs/)).toBeInTheDocument();
}); });
/*
FNXC:ArtifactsView 2026-07-10-16:20:
First-run review: the preview pane did not communicate that it is view-only. The Read-only
badge must render with the preview header in BOTH render modes (plain and markdown) — the
header markup is shared between desktop and mobile layouts.
*/
it("shows a Read-only badge in the project file preview header in plain and markdown modes", async () => {
render(<DocumentsView addToast={addToast} onOpenDetail={onOpenDetail} />);
fireEvent.click(screen.getByRole("button", { name: "Open README.md" }));
await screen.findByText(/Hello docs/);
expect(screen.getByText("Read-only")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /switch to markdown/i }));
await screen.findByText("Hello docs");
expect(screen.getByText("Read-only")).toBeInTheDocument();
});
it("sends selected plain project file preview text to a new task description", async () => { it("sends selected plain project file preview text to a new task description", async () => {
mockSelectionRect(); mockSelectionRect();
const onSendSelectionToTask = vi.fn(); const onSendSelectionToTask = vi.fn();

View File

@@ -1,3 +1,5 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { fireEvent, render, screen } from "@testing-library/react"; import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { SelectionCommentPopover, composeSelectionCommentDescription } from "../SelectionCommentPopover"; import { SelectionCommentPopover, composeSelectionCommentDescription } from "../SelectionCommentPopover";
@@ -65,6 +67,43 @@ describe("SelectionCommentPopover", () => {
expect(screen.getByRole("button", { name: /add a comment/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /add a comment/i })).toBeInTheDocument();
}); });
/*
FNXC:ArtifactsView 2026-07-10-16:20:
Regression guard for the "Add comment does nothing" bug: the trigger is positioned solely
by its transform translate, and the global `.btn:active { transform: scale(0.97) }` press
feedback replaced it while pressed, moving the button out from under the cursor so `click`
never fired. jsdom cannot reproduce hit-testing, so this invariant is asserted on the CSS:
every `.selection-comment-trigger` transform rule — base AND :active, desktop AND mobile —
must include the `translate(-50%` positioning component. The popover is shared by
DocumentsView (plain + markdown preview) and FileEditor (editor + preview), so this one
stylesheet invariant covers all surfaces.
*/
it("keeps the positioning translate in every trigger transform, including :active press state", () => {
const css = readFileSync(join(__dirname, "..", "SelectionCommentPopover.css"), "utf8");
const uncommented = css.replace(/\/\*[\s\S]*?\*\//g, "");
// Collect the declaration block of every rule whose selector list targets the trigger.
const triggerBlocks: Array<{ selector: string; block: string }> = [];
const rulePattern = /([^{}]+)\{([^{}]*)\}/g;
for (const match of uncommented.matchAll(rulePattern)) {
const selector = match[1].trim();
if (selector.split(",").some((part) => part.trim().startsWith(".selection-comment-trigger"))) {
triggerBlocks.push({ selector, block: match[2] });
}
}
const transformBlocks = triggerBlocks.filter(({ block }) => /transform\s*:/.test(block));
// Base + :active on desktop, base + :active in the mobile media query.
expect(transformBlocks.length).toBeGreaterThanOrEqual(4);
for (const { selector, block } of transformBlocks) {
expect(block, `trigger transform for "${selector}" must keep the positioning translate`).toContain("translate(-50%");
}
const activeBlocks = transformBlocks.filter(({ selector }) => selector.includes(":active"));
expect(activeBlocks.length, "both desktop and mobile need an :active override that restates the translate").toBeGreaterThanOrEqual(2);
});
it("uses a longer markdown fence when the snippet contains backticks", () => { it("uses a longer markdown fence when the snippet contains backticks", () => {
expect(composeSelectionCommentDescription({ expect(composeSelectionCommentDescription({
filePath: "README.md", filePath: "README.md",

View File

@@ -2250,6 +2250,8 @@
"projectFiles": "project files", "projectFiles": "project files",
"projectFilesTab": "Project Files", "projectFilesTab": "Project Files",
"projectMarkdownFilesLabel": "Project markdown files", "projectMarkdownFilesLabel": "Project markdown files",
"readOnly": "Read-only",
"readOnlyHint": "This preview is read-only. Select text to comment and send it to a new task.",
"resultCount_one": "{{count}} result{{plural}}", "resultCount_one": "{{count}} result{{plural}}",
"resultCount_other": "{{count}} result{{plural}}", "resultCount_other": "{{count}} result{{plural}}",
"retry": "Retry", "retry": "Retry",

View File

@@ -2282,7 +2282,9 @@
"closeLightbox": "Close artifact preview", "closeLightbox": "Close artifact preview",
"expandArtifact": "Expand {{title}}", "expandArtifact": "Expand {{title}}",
"expandArtifactHint": "Click to expand", "expandArtifactHint": "Click to expand",
"lightboxLabel": "Artifact media preview" "lightboxLabel": "Artifact media preview",
"readOnly": "",
"readOnlyHint": ""
}, },
"droidCli": { "droidCli": {
"active": "Activo", "active": "Activo",

View File

@@ -2282,7 +2282,9 @@
"closeLightbox": "Close artifact preview", "closeLightbox": "Close artifact preview",
"expandArtifact": "Expand {{title}}", "expandArtifact": "Expand {{title}}",
"expandArtifactHint": "Click to expand", "expandArtifactHint": "Click to expand",
"lightboxLabel": "Artifact media preview" "lightboxLabel": "Artifact media preview",
"readOnly": "",
"readOnlyHint": ""
}, },
"droidCli": { "droidCli": {
"active": "Actif", "active": "Actif",

View File

@@ -2282,7 +2282,9 @@
"closeLightbox": "Close artifact preview", "closeLightbox": "Close artifact preview",
"expandArtifact": "Expand {{title}}", "expandArtifact": "Expand {{title}}",
"expandArtifactHint": "Click to expand", "expandArtifactHint": "Click to expand",
"lightboxLabel": "Artifact media preview" "lightboxLabel": "Artifact media preview",
"readOnly": "",
"readOnlyHint": ""
}, },
"droidCli": { "droidCli": {
"active": "활성", "active": "활성",

View File

@@ -2282,7 +2282,9 @@
"closeLightbox": "Close artifact preview", "closeLightbox": "Close artifact preview",
"expandArtifact": "Expand {{title}}", "expandArtifact": "Expand {{title}}",
"expandArtifactHint": "Click to expand", "expandArtifactHint": "Click to expand",
"lightboxLabel": "Artifact media preview" "lightboxLabel": "Artifact media preview",
"readOnly": "",
"readOnlyHint": ""
}, },
"droidCli": { "droidCli": {
"active": "活跃", "active": "活跃",

View File

@@ -2282,7 +2282,9 @@
"closeLightbox": "Close artifact preview", "closeLightbox": "Close artifact preview",
"expandArtifact": "Expand {{title}}", "expandArtifact": "Expand {{title}}",
"expandArtifactHint": "Click to expand", "expandArtifactHint": "Click to expand",
"lightboxLabel": "Artifact media preview" "lightboxLabel": "Artifact media preview",
"readOnly": "",
"readOnlyHint": ""
}, },
"droidCli": { "droidCli": {
"active": "活躍", "active": "活躍",