diff --git a/.changeset/fn-7454-linear-plugin-visibility.md b/.changeset/fn-7454-linear-plugin-visibility.md
new file mode 100644
index 0000000000..38b7690719
--- /dev/null
+++ b/.changeset/fn-7454-linear-plugin-visibility.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Show the bundled Linear Import plugin in Plugin Manager and dashboard plugin surfaces.
+category: fix
+dev: Keeps fusion-plugin-linear-import registered across the built-in Plugin Manager catalog while reusing existing registry, dashboard view, and bundled packaging paths.
diff --git a/packages/dashboard/app/components/PluginManager.tsx b/packages/dashboard/app/components/PluginManager.tsx
index 0bf4448334..5061d0fffa 100644
--- a/packages/dashboard/app/components/PluginManager.tsx
+++ b/packages/dashboard/app/components/PluginManager.tsx
@@ -176,6 +176,17 @@ export const BUILTIN_PLUGINS: BuiltinPlugin[] = [
category: "integration",
path: "./plugins/fusion-plugin-compound-engineering",
},
+ /*
+ * FNXC:PluginManager 2026-07-02-17:56:
+ * FN-7454 keeps Linear Import in the built-in catalog because FN-7443 shipped the plugin package, registry entry, and dashboard view, but users still could not install or manage it from Plugin Manager without this bundled-plugin registration.
+ */
+ {
+ id: "fusion-plugin-linear-import",
+ name: "Linear Import",
+ description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
+ category: "integration",
+ path: "./plugins/fusion-plugin-linear-import",
+ },
{
id: BUILTIN_AGENT_BROWSER_PLUGIN_ID,
name: "Agent Browser",
diff --git a/packages/dashboard/app/components/__tests__/PluginManager.registry.test.tsx b/packages/dashboard/app/components/__tests__/PluginManager.registry.test.tsx
index 8757cc0d4e..0622d8efc0 100644
--- a/packages/dashboard/app/components/__tests__/PluginManager.registry.test.tsx
+++ b/packages/dashboard/app/components/__tests__/PluginManager.registry.test.tsx
@@ -97,8 +97,8 @@ function stubEventSource() {
vi.stubGlobal("EventSource", MockEventSource);
}
-async function renderRegistry(entries: RegistryPluginEntry[] = registryEntries) {
- vi.mocked(fetchPlugins).mockResolvedValue([installedPlugin]);
+async function renderRegistry(entries: RegistryPluginEntry[] = registryEntries, installed: PluginInstallation[] = [installedPlugin]) {
+ vi.mocked(fetchPlugins).mockResolvedValue(installed);
vi.mocked(fetchPluginRegistry).mockResolvedValue(entries);
render();
await act(async () => {
@@ -151,6 +151,74 @@ describe("PluginManager registry browsing", () => {
expect(within(comingSoon).getByText("Coming Soon")).toBeInTheDocument();
});
+ it.each([
+ {
+ state: "not-installed" as const,
+ entry: {
+ id: "fusion-plugin-linear-import",
+ name: "Linear Import",
+ description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
+ version: "0.1.0",
+ author: "Fusion",
+ category: "integration" as const,
+ path: "./plugins/fusion-plugin-linear-import",
+ tags: ["linear", "import", "issues", "dashboard"],
+ installed: false,
+ canInstall: true,
+ },
+ installed: [] as PluginInstallation[],
+ action: "Install",
+ },
+ {
+ state: "installed-error" as const,
+ entry: {
+ id: "fusion-plugin-linear-import",
+ name: "Linear Import",
+ description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
+ version: "0.1.0",
+ author: "Fusion",
+ category: "integration" as const,
+ path: "./plugins/fusion-plugin-linear-import",
+ tags: ["linear", "import", "issues", "dashboard"],
+ installed: true,
+ installedVersion: "0.1.0",
+ state: "error" as const,
+ canInstall: true,
+ },
+ installed: [{ ...installedPlugin, id: "fusion-plugin-linear-import", name: "Linear Import", state: "error" as const, enabled: true, error: "Linear plugin failed" }],
+ action: "Manage",
+ },
+ {
+ state: "installed-started" as const,
+ entry: {
+ id: "fusion-plugin-linear-import",
+ name: "Linear Import",
+ description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
+ version: "0.1.0",
+ author: "Fusion",
+ category: "integration" as const,
+ path: "./plugins/fusion-plugin-linear-import",
+ tags: ["linear", "import", "issues", "dashboard"],
+ installed: true,
+ installedVersion: "0.1.0",
+ state: "started" as const,
+ canInstall: true,
+ },
+ installed: [{ ...installedPlugin, id: "fusion-plugin-linear-import", name: "Linear Import", state: "started" as const, enabled: true }],
+ action: "Manage",
+ },
+ ])("shows Linear Import registry action for $state", async ({ entry, installed, action }) => {
+ await renderRegistry([entry], installed);
+
+ const section = screen.getByRole("region", { name: "Browse Registry" });
+ const linear = within(section).getByText("Linear Import").closest(".plugin-registry-item") as HTMLElement;
+ expect(linear).toBeInTheDocument();
+ expect(within(linear).getByText("Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.")).toBeInTheDocument();
+ expect(within(linear).getByText("integration")).toBeInTheDocument();
+ expect(within(linear).getByRole("button", { name: action })).toBeInTheDocument();
+ expect(within(section).getAllByText("Linear Import")).toHaveLength(1);
+ });
+
it("installs registry plugins with their manifest path and refreshes installed plugins", async () => {
await renderRegistry();
expect(fetchPlugins).toHaveBeenCalledTimes(1);
diff --git a/packages/dashboard/app/components/__tests__/PluginManager.test.tsx b/packages/dashboard/app/components/__tests__/PluginManager.test.tsx
index b7d8d6bd5d..2d274398d2 100644
--- a/packages/dashboard/app/components/__tests__/PluginManager.test.tsx
+++ b/packages/dashboard/app/components/__tests__/PluginManager.test.tsx
@@ -113,6 +113,7 @@ vi.mock("../../hooks/useConfirm", () => ({
import {
AGENT_BROWSER_SETTINGS_SCHEMA,
BUILTIN_AGENT_BROWSER_PLUGIN_ID,
+ BUILTIN_PLUGINS,
PluginManager,
STATE_COLORS,
} from "../PluginManager";
@@ -131,6 +132,15 @@ import {
} from "../../api";
const addToast = vi.fn();
+const LINEAR_PLUGIN_ID = "fusion-plugin-linear-import";
+
+function getBuiltInPluginCard(name: string): HTMLElement {
+ const card = screen.getAllByText(name)
+ .map((node) => node.closest(".plugin-builtins-item"))
+ .find((node): node is HTMLElement => node instanceof HTMLElement);
+ expect(card).toBeTruthy();
+ return card;
+}
function expectEventsUrl(url: string, projectId?: string) {
const parsed = new URL(url, "http://localhost");
@@ -271,9 +281,79 @@ describe("PluginManager", () => {
expect(screen.getByText("Reports")).toBeTruthy();
expect(screen.getByText("WhatsApp Chat")).toBeTruthy();
expect(screen.getByText("CLI Printing Press")).toBeTruthy();
+ expect(screen.getByText("Linear Import")).toBeTruthy();
expect(screen.getByText(/Pairs to WhatsApp Web \(multi-device\) with QR or pairing code/i)).toBeTruthy();
});
+ it("keeps Linear Import in the bundled plugin catalog", () => {
+ expect(BUILTIN_PLUGINS.find((plugin) => plugin.id === LINEAR_PLUGIN_ID)).toMatchObject({
+ id: LINEAR_PLUGIN_ID,
+ name: "Linear Import",
+ category: "integration",
+ path: "./plugins/fusion-plugin-linear-import",
+ description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
+ });
+ });
+
+ it("installs Linear Import from the built-in section when not installed", async () => {
+ vi.mocked(fetchPlugins).mockResolvedValue([]);
+ render();
+
+ await waitFor(() => {
+ expect(fetchPlugins).toHaveBeenCalled();
+ });
+
+ const linearCard = getBuiltInPluginCard("Linear Import");
+ expect(within(linearCard).getByText("Not installed")).toBeTruthy();
+ const installButton = within(linearCard).getByRole("button", { name: /Install Linear Import/i });
+ await userEvent.click(installButton);
+
+ await waitFor(() => {
+ expect(installPlugin).toHaveBeenCalledWith({ path: "./plugins/fusion-plugin-linear-import" }, undefined);
+ expect(addToast).toHaveBeenCalledWith("Linear Import installed globally", "success");
+ });
+ });
+
+ it.each([
+ { state: "installed" as const, enabled: false, statusLabel: "installed" },
+ { state: "started" as const, enabled: true, statusLabel: "started" },
+ { state: "error" as const, enabled: true, statusLabel: "error" },
+ ])("shows Manage for installed Linear Import in $state state", async ({ state, enabled, statusLabel }) => {
+ vi.mocked(fetchPlugins).mockResolvedValue([
+ {
+ ...mockPlugins[0],
+ id: LINEAR_PLUGIN_ID,
+ name: "Linear Import",
+ description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
+ state,
+ enabled,
+ error: state === "error" ? "Linear plugin failed to start" : undefined,
+ settingsSchema: {},
+ },
+ ]);
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getAllByText("Linear Import").length).toBeGreaterThanOrEqual(2);
+ });
+
+ const linearCard = getBuiltInPluginCard("Linear Import");
+ expect(within(linearCard).getByText("Installed")).toBeTruthy();
+ expect(within(linearCard).queryByText("Built-in metadata only")).toBeNull();
+ expect(within(linearCard).getAllByText("integration").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText(statusLabel).length).toBeGreaterThanOrEqual(1);
+
+ const manageButton = within(linearCard).getByRole("button", { name: /^Manage$/i });
+ expect(manageButton).not.toBeDisabled();
+ await userEvent.click(manageButton);
+
+ await waitFor(() => {
+ expect(fetchPluginSettings).toHaveBeenCalledWith(LINEAR_PLUGIN_ID, undefined);
+ });
+ expect(screen.getByTestId("plugin-manager-detail")).toBeTruthy();
+ });
+
it("renders built-in agent browser metadata-only entry when uninstalled", async () => {
render();