diff --git a/.changeset/theme-mailbox-markdown-links.md b/.changeset/theme-mailbox-markdown-links.md new file mode 100644 index 0000000000..baf3cec9b4 --- /dev/null +++ b/.changeset/theme-mailbox-markdown-links.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Mailbox message links now use theme colors instead of default browser blue. +category: fix +dev: Markdown anchor states use dashboard tokens in shared MailboxMessageContent styling. diff --git a/packages/dashboard/app/components/MailboxModal.css b/packages/dashboard/app/components/MailboxModal.css index c6f9c2d1a5..9d3df9a6f1 100644 --- a/packages/dashboard/app/components/MailboxModal.css +++ b/packages/dashboard/app/components/MailboxModal.css @@ -411,6 +411,35 @@ Artifact-registration mail messages now render metadata-driven media and produci padding-left: var(--space-lg); } +/* +FNXC:Mailbox 2026-07-26-14:39: +Markdown links in mailbox message bodies must use theme tokens: browser default blue +and visited purple clash with dark themes and violate the dashboard token system. +*/ +.mailbox-markdown a { + color: var(--accent); + text-decoration: underline; + text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); + text-underline-offset: var(--space-2xs); + border-radius: var(--radius-sm); + overflow-wrap: anywhere; + transition: color var(--transition-fast), text-decoration-color var(--transition-fast); +} + +.mailbox-markdown a:visited { + color: var(--accent); +} + +.mailbox-markdown a:hover { + color: color-mix(in srgb, var(--accent) 85%, var(--text)); + text-decoration-color: var(--accent); +} + +.mailbox-markdown a:focus-visible { + outline: none; + box-shadow: var(--focus-ring-strong); +} + .mailbox-markdown :not(pre) > code { padding: 0 var(--space-xs); border-radius: var(--radius-sm); diff --git a/packages/dashboard/app/components/__tests__/MailboxMessageContent.test.tsx b/packages/dashboard/app/components/__tests__/MailboxMessageContent.test.tsx index a017523f99..3203e3eadf 100644 --- a/packages/dashboard/app/components/__tests__/MailboxMessageContent.test.tsx +++ b/packages/dashboard/app/components/__tests__/MailboxMessageContent.test.tsx @@ -1,9 +1,13 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; import { describe, it, expect, afterEach, vi } from "vitest"; import { render, cleanup, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { FileBrowserProvider } from "../../context/FileBrowserContext"; import { MailboxMessageContent } from "../MailboxMessageContent"; +const mailboxModalCss = readFileSync(resolve(__dirname, "../MailboxModal.css"), "utf8"); + // FNXC:Markdown 2026-06-23-03:15: Mock the heavy `mermaid` library so the mermaid // rendering tests do not pull in the real parser/renderer bundle. The component // lazy-imports `mermaid` (default export), so we mock the module default. @@ -61,15 +65,44 @@ describe("MailboxMessageContent", () => { expect(pre?.textContent).toContain("npm install"); }); - it("renders links with target=_blank and noopener noreferrer", () => { - render( - , + it("themes markdown links consistently across inline, autolink, list, and table content", () => { + const content = [ + "See [inline docs](https://example.com/inline).", + "Visit https://example.com/autolink.", + "- [list docs](https://example.com/list)", + "", + "| docs |", + "| --- |", + "| [table docs](https://example.com/table) |", + "", + "See packages/dashboard/app/App.tsx for context.", + ].join("\n"); + const { container } = render( + + + , ); - const link = screen.getByRole("link", { name: "docs" }); - expect(link.getAttribute("href")).toBe("https://example.com/docs"); - expect(link.getAttribute("target")).toBe("_blank"); - expect(link.getAttribute("rel")).toContain("noopener"); - expect(link.getAttribute("rel")).toContain("noreferrer"); + + const links = screen.getAllByRole("link"); + expect(links).toHaveLength(4); + for (const link of links) { + expect(link.closest(".mailbox-markdown")).not.toBeNull(); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); + } + expect(screen.getByRole("button", { name: "packages/dashboard/app/App.tsx" })) + .toHaveClass("file-path-link"); + expect(container.querySelector("a.file-path-link")).toBeNull(); + }); + + it("defines token-only mailbox markdown anchor styles", () => { + const anchorRule = mailboxModalCss.match(/\.mailbox-markdown a\s*\{([^}]*)\}/)?.[1]; + expect(mailboxModalCss).toMatch(/\.mailbox-markdown a\b/); + expect(anchorRule).toBeDefined(); + expect(anchorRule).not.toMatch(/#[0-9a-f]{3,8}\b|rgba\(/i); + expect(mailboxModalCss).toMatch(/\.mailbox-markdown a:visited\s*\{/); + expect(mailboxModalCss).toMatch(/\.mailbox-markdown a:hover\s*\{/); + expect(mailboxModalCss).toMatch(/\.mailbox-markdown a:focus-visible\s*\{/); }); it("renders GFM tables with the mailbox-scoped class", () => {