FN-8487: render Oh My Pi logo in gradient

Render the shared Oh My Pi provider icon with its official colorful gradient.\n\n- Add a scoped SVG gradient with unique IDs for simultaneous OMP icons.\n- Define reusable OMP gradient tokens and retain the provider color fallback.\n- Cover gradient rendering and ID uniqueness in provider icon tests.\n\nFiles changed:\n packages/dashboard/app/components/ProviderIcon.tsx | 18 ++++++++++---\n .../app/components/__tests__/ProviderIcon.test.tsx | 31 ++++++++++++++++++++--\n packages/dashboard/app/styles.css                  |  5 +++-\n 3 files changed, 47 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-8487

Fusion-Task-Lineage: 6b507791-934f-443b-9c93-f59e5ee99ddf

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-22 09:11:42 -07:00
parent e514e134da
commit f937482b76
3 changed files with 47 additions and 7 deletions

View File

@@ -1,3 +1,4 @@
import { useId } from "react";
import { Cpu } from "lucide-react";
import { inferProviderIconKey } from "../utils/providerIconKey";
@@ -527,11 +528,13 @@ function GitHubIcon({ size, color, label = "GitHub" }: { size: number; color: st
}
/*
FNXC:ProviderIcons 2026-07-18-18:24:
FN-8354: OMP ACP is a first-class provider, so its shared dashboard icon must use the official omp.sh favicon's stepped T mark rather than the generic Cpu fallback. Render its official single-color silhouette with the provider token so it remains legible in either theme.
FNXC:ProviderIcons 2026-07-22-00:00:
FN-8487: The shared OMP mark must retain the official omp.sh favicon's pink-to-purple-to-cyan diagonal gradient instead of adapting to the theme as a monochrome glyph. Keep the favicon's stepped-T path but omit its dark rounded tile so the mark remains transparent on dashboard chrome.
Source: https://omp.sh/favicon.svg
*/
function OmpIcon({ size, color, label = "Oh My Pi" }: { size: number; color: string; label?: string }) {
function OmpIcon({ size, label = "Oh My Pi" }: { size: number; color: string; label?: string }) {
const gradientId = `omp-gradient-${useId().replace(/:/g, "")}`;
return (
<svg
width={size}
@@ -542,7 +545,14 @@ function OmpIcon({ size, color, label = "Oh My Pi" }: { size: number; color: str
data-testid="omp-icon"
aria-label={label}
>
<path d="M14 16h36v8H40v32h-8V24h-6v22h-8V24h-4z" fill={color} />
<defs>
<linearGradient id={gradientId} x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stopColor="var(--provider-omp-gradient-start)" />
<stop offset="0.5" stopColor="var(--provider-omp-gradient-mid)" />
<stop offset="1" stopColor="var(--provider-omp-gradient-end)" />
</linearGradient>
</defs>
<path d="M14 16h36v8H40v32h-8V24h-6v22h-8V24h-4z" fill={`url(#${gradientId})`} />
</svg>
);
}

View File

@@ -74,14 +74,41 @@ describe("ProviderIcon", () => {
expect(document.querySelector('[data-provider="cursor"] svg:not([data-testid])')).not.toBeInTheDocument();
});
it("renders the OMP brand icon, auth label, and tokenized color for omp-cli", () => {
it("renders the OMP brand icon, auth label, and official tokenized gradient for omp-cli", () => {
render(<ProviderIcon provider="omp-cli" />);
const svg = screen.getByTestId("omp-icon");
const gradient = svg.querySelector("linearGradient");
const path = svg.querySelector("path");
expect(svg).toBeInTheDocument();
expect(screen.getByLabelText("Oh My Pi — via omp ACP")).toBeInTheDocument();
expect(svg.parentElement).toHaveAttribute("data-provider", "omp-cli");
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-omp)" });
expect(svg.querySelector("path")).toHaveAttribute("fill", "var(--provider-omp)");
expect(path).toHaveAttribute("fill", `url(#${gradient?.id})`);
expect(gradient).toHaveAttribute("x1", "0");
expect(gradient).toHaveAttribute("y1", "0");
expect(gradient).toHaveAttribute("x2", "1");
expect(gradient).toHaveAttribute("y2", "1");
expect(Array.from(gradient?.querySelectorAll("stop") ?? []).map((stop) => stop.getAttribute("stop-color"))).toEqual([
"var(--provider-omp-gradient-start)",
"var(--provider-omp-gradient-mid)",
"var(--provider-omp-gradient-end)",
]);
});
it("assigns unique gradient ids to simultaneous OMP icons", () => {
const { container } = render(
<>
<ProviderIcon provider="omp-cli" />
<ProviderIcon provider="omp" />
</>,
);
const gradients = Array.from(container.querySelectorAll("[data-testid='omp-icon'] linearGradient"));
const paths = Array.from(container.querySelectorAll("[data-testid='omp-icon'] path"));
expect(gradients).toHaveLength(2);
expect(new Set(gradients.map((gradient) => gradient.id)).size).toBe(2);
expect(paths.map((path) => path.getAttribute("fill"))).toEqual(gradients.map((gradient) => `url(#${gradient.id})`));
});
it.each(["omp", "oh-my-pi"])("renders the OMP brand icon for the %s alias", (provider) => {

View File

@@ -396,7 +396,10 @@ svg.spinning {
--provider-cursor-cli: #7c3aed;
--provider-ollama: #d4a27f;
/* Runtime-plugin marks. */
--provider-omp: var(--text);
--provider-omp-gradient-start: #ed4abf;
--provider-omp-gradient-mid: #9b4dff;
--provider-omp-gradient-end: #5ad8e6;
--provider-omp: var(--provider-omp-gradient-mid);
--provider-hermes: #d4961c;
--provider-openclaw: #ff4f40;
--provider-paperclip: var(--text);