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:
7
.changeset/artifacts-add-comment-active-transform.md
Normal file
7
.changeset/artifacts-add-comment-active-transform.md
Normal 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.
|
||||
@@ -318,6 +318,19 @@ Artifacts controls are the first page content below the shared header, so add a
|
||||
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 {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
@@ -744,6 +744,16 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac
|
||||
<div className="documents-content-viewer">
|
||||
<div className="documents-content-header">
|
||||
<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
|
||||
className="btn btn-sm document-mode-toggle"
|
||||
onClick={() => setRenderProjectMarkdown((prev) => !prev)}
|
||||
|
||||
@@ -12,6 +12,24 @@
|
||||
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 {
|
||||
width: min(var(--selection-comment-panel-width, calc(var(--space-2xl) * 12)), calc(100vw - (var(--space-lg) * 2)));
|
||||
transform: translate(-50%, var(--space-xs));
|
||||
@@ -62,6 +80,16 @@
|
||||
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 {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
@@ -567,6 +567,24 @@ describe("DocumentsView", () => {
|
||||
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 () => {
|
||||
mockSelectionRect();
|
||||
const onSendSelectionToTask = vi.fn();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { SelectionCommentPopover, composeSelectionCommentDescription } from "../SelectionCommentPopover";
|
||||
@@ -65,6 +67,43 @@ describe("SelectionCommentPopover", () => {
|
||||
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", () => {
|
||||
expect(composeSelectionCommentDescription({
|
||||
filePath: "README.md",
|
||||
|
||||
@@ -2250,6 +2250,8 @@
|
||||
"projectFiles": "project files",
|
||||
"projectFilesTab": "Project 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_other": "{{count}} result{{plural}}",
|
||||
"retry": "Retry",
|
||||
|
||||
@@ -2282,7 +2282,9 @@
|
||||
"closeLightbox": "Close artifact preview",
|
||||
"expandArtifact": "Expand {{title}}",
|
||||
"expandArtifactHint": "Click to expand",
|
||||
"lightboxLabel": "Artifact media preview"
|
||||
"lightboxLabel": "Artifact media preview",
|
||||
"readOnly": "",
|
||||
"readOnlyHint": ""
|
||||
},
|
||||
"droidCli": {
|
||||
"active": "Activo",
|
||||
|
||||
@@ -2282,7 +2282,9 @@
|
||||
"closeLightbox": "Close artifact preview",
|
||||
"expandArtifact": "Expand {{title}}",
|
||||
"expandArtifactHint": "Click to expand",
|
||||
"lightboxLabel": "Artifact media preview"
|
||||
"lightboxLabel": "Artifact media preview",
|
||||
"readOnly": "",
|
||||
"readOnlyHint": ""
|
||||
},
|
||||
"droidCli": {
|
||||
"active": "Actif",
|
||||
|
||||
@@ -2282,7 +2282,9 @@
|
||||
"closeLightbox": "Close artifact preview",
|
||||
"expandArtifact": "Expand {{title}}",
|
||||
"expandArtifactHint": "Click to expand",
|
||||
"lightboxLabel": "Artifact media preview"
|
||||
"lightboxLabel": "Artifact media preview",
|
||||
"readOnly": "",
|
||||
"readOnlyHint": ""
|
||||
},
|
||||
"droidCli": {
|
||||
"active": "활성",
|
||||
|
||||
@@ -2282,7 +2282,9 @@
|
||||
"closeLightbox": "Close artifact preview",
|
||||
"expandArtifact": "Expand {{title}}",
|
||||
"expandArtifactHint": "Click to expand",
|
||||
"lightboxLabel": "Artifact media preview"
|
||||
"lightboxLabel": "Artifact media preview",
|
||||
"readOnly": "",
|
||||
"readOnlyHint": ""
|
||||
},
|
||||
"droidCli": {
|
||||
"active": "活跃",
|
||||
|
||||
@@ -2282,7 +2282,9 @@
|
||||
"closeLightbox": "Close artifact preview",
|
||||
"expandArtifact": "Expand {{title}}",
|
||||
"expandArtifactHint": "Click to expand",
|
||||
"lightboxLabel": "Artifact media preview"
|
||||
"lightboxLabel": "Artifact media preview",
|
||||
"readOnly": "",
|
||||
"readOnlyHint": ""
|
||||
},
|
||||
"droidCli": {
|
||||
"active": "活躍",
|
||||
|
||||
Reference in New Issue
Block a user