diff --git a/.changeset/artifacts-add-comment-active-transform.md b/.changeset/artifacts-add-comment-active-transform.md
new file mode 100644
index 0000000000..1b02672703
--- /dev/null
+++ b/.changeset/artifacts-add-comment-active-transform.md
@@ -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.
diff --git a/packages/dashboard/app/components/DocumentsView.css b/packages/dashboard/app/components/DocumentsView.css
index 3690df2c09..781950b547 100644
--- a/packages/dashboard/app/components/DocumentsView.css
+++ b/packages/dashboard/app/components/DocumentsView.css
@@ -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);
diff --git a/packages/dashboard/app/components/DocumentsView.tsx b/packages/dashboard/app/components/DocumentsView.tsx
index a7aaebf1c5..3ea4b70ce4 100644
--- a/packages/dashboard/app/components/DocumentsView.tsx
+++ b/packages/dashboard/app/components/DocumentsView.tsx
@@ -744,6 +744,16 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onOpenArtifac
{selectedFile.path}
+ {/*
+ 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.
+ */}
+
+ {t("documents.readOnly", "Read-only")}
+
setRenderProjectMarkdown((prev) => !prev)}
diff --git a/packages/dashboard/app/components/SelectionCommentPopover.css b/packages/dashboard/app/components/SelectionCommentPopover.css
index 83c1609803..a71ddcc3fd 100644
--- a/packages/dashboard/app/components/SelectionCommentPopover.css
+++ b/packages/dashboard/app/components/SelectionCommentPopover.css
@@ -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;
}
diff --git a/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx b/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx
index 984bd5c4a0..8024b9c56f 100644
--- a/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx
+++ b/packages/dashboard/app/components/__tests__/DocumentsView.test.tsx
@@ -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( );
+
+ 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();
diff --git a/packages/dashboard/app/components/__tests__/SelectionCommentPopover.test.tsx b/packages/dashboard/app/components/__tests__/SelectionCommentPopover.test.tsx
index 8689e7938e..97b08d8240 100644
--- a/packages/dashboard/app/components/__tests__/SelectionCommentPopover.test.tsx
+++ b/packages/dashboard/app/components/__tests__/SelectionCommentPopover.test.tsx
@@ -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",
diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json
index 94fa917150..cae0adf933 100644
--- a/packages/i18n/locales/en/app.json
+++ b/packages/i18n/locales/en/app.json
@@ -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",
diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json
index 1bb8c7673d..36dd81980f 100644
--- a/packages/i18n/locales/es/app.json
+++ b/packages/i18n/locales/es/app.json
@@ -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",
diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json
index 2c5902c76d..fe00de3887 100644
--- a/packages/i18n/locales/fr/app.json
+++ b/packages/i18n/locales/fr/app.json
@@ -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",
diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json
index 7a63eacfc9..c4eb14779f 100644
--- a/packages/i18n/locales/ko/app.json
+++ b/packages/i18n/locales/ko/app.json
@@ -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": "활성",
diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json
index 6540ac301f..8d1b77e203 100644
--- a/packages/i18n/locales/zh-CN/app.json
+++ b/packages/i18n/locales/zh-CN/app.json
@@ -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": "活跃",
diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json
index 8b47d4e838..8f5ca4e8d2 100644
--- a/packages/i18n/locales/zh-TW/app.json
+++ b/packages/i18n/locales/zh-TW/app.json
@@ -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": "活躍",