diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx
index efdadbd83..3c7598a0a 100644
--- a/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx
+++ b/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx
@@ -120,6 +120,11 @@ const ComponentMarkup = ({
owned,
published_by: publishedBy,
} = component;
+ const componentIdentifier = publishedBy
+ ? `Published by ${publishedBy}`
+ : digest
+ ? `Digest ${digest}`
+ : undefined;
const displayName = useMemo(
() => name ?? getComponentName({ spec, url }),
@@ -267,14 +272,14 @@ const ComponentMarkup = ({
>
{displayName}
- {publishedBy && (
+ {componentIdentifier && (
- Published by {publishedBy}
+ {componentIdentifier}
)}
{matchExplanation && (
diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx
new file mode 100644
index 000000000..cfc1e00f1
--- /dev/null
+++ b/src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx
@@ -0,0 +1,99 @@
+import { render, screen } from "@testing-library/react";
+import type { ReactNode } from "react";
+import { describe, expect, it, vi } from "vitest";
+
+import type { SearchResult } from "@/types/componentLibrary";
+import { ComponentSearchFilter } from "@/utils/constants";
+
+import SearchResults from "./SearchResults";
+
+vi.mock("@/components/shared/Dialogs", () => ({
+ ComponentDetailsDialog: () => ,
+}));
+
+vi.mock("@/components/shared/FavoriteComponentToggle", () => ({
+ ComponentFavoriteToggle: () => ,
+}));
+
+vi.mock(
+ "@/components/shared/ManageComponent/hooks/useOutdatedComponents",
+ () => ({
+ useOutdatedComponents: () => ({ data: [] }),
+ }),
+);
+
+vi.mock("@/components/shared/Settings/useFlags", () => ({
+ useFlagValue: () => false,
+}));
+
+vi.mock("@/providers/ComponentLibraryProvider/ForcedSearchProvider", () => ({
+ useForcedSearchContext: () => ({
+ currentSearchFilter: {
+ searchTerm: "scrape",
+ filters: [ComponentSearchFilter.NAME],
+ },
+ }),
+}));
+
+vi.mock("../../NodesOverlay/NodesOverlayProvider", () => ({
+ useNodesOverlay: () => ({
+ notifyNode: vi.fn(),
+ getNodeIdsByDigest: vi.fn(() => []),
+ fitNodeIntoView: vi.fn(),
+ }),
+}));
+
+vi.mock("./ComponentHoverPopover", () => ({
+ ComponentHoverPopover: ({ children }: { children: ReactNode }) => (
+ <>{children}>
+ ),
+}));
+
+describe("SearchResults", () => {
+ it("shows publisher metadata for v1 component search results", () => {
+ const searchResult: SearchResult = {
+ components: {
+ standard: [
+ {
+ digest: "published-digest",
+ name: "Scrape V2",
+ published_by: "pipeline-components@shopify.com",
+ },
+ ],
+ user: [],
+ used: [],
+ },
+ };
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Scrape V2")).toBeInTheDocument();
+ expect(
+ screen.getByText("Published by pipeline-components@shopify.com"),
+ ).toBeInTheDocument();
+ });
+
+ it("shows digest metadata when a v1 component search result has no publisher", () => {
+ const searchResult: SearchResult = {
+ components: {
+ standard: [
+ {
+ digest: "sha256:abc123",
+ name: "Upload to GCS",
+ },
+ ],
+ user: [],
+ used: [],
+ },
+ };
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Upload to GCS")).toBeInTheDocument();
+ expect(screen.getByText("Digest sha256:abc123")).toBeInTheDocument();
+ });
+});
diff --git a/src/routes/Dashboard/DashboardComponentsV2View.test.tsx b/src/routes/Dashboard/DashboardComponentsV2View.test.tsx
index 72bfa0ac4..b9fed3f39 100644
--- a/src/routes/Dashboard/DashboardComponentsV2View.test.tsx
+++ b/src/routes/Dashboard/DashboardComponentsV2View.test.tsx
@@ -35,6 +35,10 @@ const routeMocks = vi.hoisted(() => {
return {
standard: makeComponent("standard-digest", "Standard component"),
registered: makeComponent("registered-digest", "Registered component"),
+ published: {
+ ...makeComponent("published-digest", "Published component"),
+ published_by: "pipeline-components@shopify.com",
+ },
user: makeComponent("user-digest", "User component"),
extraStandardComponents: [] as ComponentReference[],
navigate: vi.fn(),
@@ -81,7 +85,7 @@ vi.mock("@tanstack/react-query", () => ({
}
if (key === "component-search-v2" && queryKey[1] === "published") {
- return { data: [], isLoading: false };
+ return { data: [routeMocks.published], isLoading: false };
}
if (
@@ -108,6 +112,7 @@ vi.mock("@tanstack/react-query", () => ({
data: [
routeMocks.standard,
routeMocks.registered,
+ routeMocks.published,
routeMocks.user,
...routeMocks.extraStandardComponents,
],
@@ -536,13 +541,13 @@ describe("DashboardComponentsV2View", () => {
expect(
screen.getByText(
- "Showing 100 of 108 components in selected sources. Start typing to search.",
+ "Showing 100 of 109 components in selected sources. Start typing to search.",
),
).toBeInTheDocument();
expect(screen.getByText("Browse component 099")).toBeInTheDocument();
expect(screen.queryByText("Browse component 100")).not.toBeInTheDocument();
- fireEvent.click(screen.getByRole("button", { name: "Show 8 more" }));
+ fireEvent.click(screen.getByRole("button", { name: "Show 9 more" }));
await waitFor(() => {
expect(screen.getByText("Browse component 104")).toBeInTheDocument();
@@ -635,6 +640,34 @@ describe("DashboardComponentsV2View", () => {
});
});
+ it("shows publisher on published component result cards", async () => {
+ render();
+
+ fireEvent.change(screen.getByLabelText("Search components"), {
+ target: { value: "published" },
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText("Published component")).toBeInTheDocument();
+ });
+ expect(
+ screen.getByText("Published by pipeline-components@shopify.com"),
+ ).toBeInTheDocument();
+ });
+
+ it("keeps publisher visible in compact selected-result rows", async () => {
+ routeMocks.search = { component: "published-digest", q: "published" };
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByText("Published component")).toBeInTheDocument();
+ });
+ expect(
+ screen.getByText("Published by pipeline-components@shopify.com"),
+ ).toBeInTheDocument();
+ });
+
it("explains why lexical component results matched", async () => {
render();
diff --git a/src/routes/Dashboard/DashboardComponentsV2View.tsx b/src/routes/Dashboard/DashboardComponentsV2View.tsx
index 9d945748c..ef7184589 100644
--- a/src/routes/Dashboard/DashboardComponentsV2View.tsx
+++ b/src/routes/Dashboard/DashboardComponentsV2View.tsx
@@ -372,9 +372,14 @@ const ComponentCard = ({
)}
- {publishedBy && !isDetailOpen && (
-
- by {publishedBy}
+ {publishedBy && (
+
+ Published by {publishedBy}
)}
{matchSummary && (
diff --git a/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx b/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx
index 69cd6dfa0..3548e05ec 100644
--- a/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx
+++ b/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx
@@ -12,13 +12,16 @@ vi.mock(
ComponentMarkup: ({
component,
matchedFields,
+ source,
}: {
component: { name?: string };
matchedFields?: string[];
+ source?: { label: string };
}) => (
{component.name}
{matchedFields && matched {matchedFields.join(",")}}
+ {source && source {source.label}}
),
IONodeSidebarItem: () => IO node,
@@ -109,6 +112,25 @@ describe("ComponentSearchResults", () => {
expect(onSuggestedSearch).toHaveBeenCalledWith("csv");
});
+ it("passes source metadata through for result display", () => {
+ const results: ComponentSearchV2Result[] = [
+ {
+ reference: {
+ digest: "digest",
+ name: "Load CSV",
+ published_by: "pipeline-components@shopify.com",
+ },
+ source: { kind: "published", id: "published", label: "Published" },
+ },
+ ];
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText("source Published")).toBeInTheDocument();
+ });
+
it("passes matched fields through for result explanations", () => {
const results: ComponentSearchV2Result[] = [
{
diff --git a/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx b/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx
index d7c40761e..f6ccf1ab8 100644
--- a/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx
+++ b/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx
@@ -154,6 +154,7 @@ export function ComponentSearchResults({
matchedFields={result.matchedFields}
rerankScore={result.rerankScore}
rerankReason={result.rerankReason}
+ source={result.source}
/>
))}