fix(FN-1801): plugin settings UX and theme awareness improvements

- Add new input types for plugin settings (password, textarea, array fields)
- Render toggle switch inside .toggle-switch label for theme-aware styling
- Update STATE_COLORS to use CSS variable references instead of hardcoded hex values
- Add SSE live updates for plugin lifecycle events (enabled, disabled, uninstalled, settings-updated)
- Project-scoped SSE subscription when projectId is provided
- Expand PluginManager tests with 32 passing tests covering new functionality
This commit is contained in:
Fusion
2026-04-15 03:29:08 -07:00
committed by gsxdsm
parent 53305684bb
commit 4f4b06605d
3 changed files with 98 additions and 8 deletions

View File

@@ -39,12 +39,12 @@ interface PluginManagerProps {
projectId?: string; projectId?: string;
} }
const STATE_COLORS: Record<string, string> = { export const STATE_COLORS: Record<string, string> = {
started: "var(--color-success, #22c55e)", started: "var(--color-success)",
loaded: "var(--color-warning, #eab308)", loaded: "var(--color-warning)",
error: "var(--color-error, #ef4444)", error: "var(--color-error)",
stopped: "var(--color-muted, #6b7280)", stopped: "var(--color-muted)",
installed: "var(--color-info, #3b82f6)", installed: "var(--color-info)",
}; };
export function PluginManager({ addToast, projectId }: PluginManagerProps) { export function PluginManager({ addToast, projectId }: PluginManagerProps) {

View File

@@ -81,7 +81,7 @@ vi.mock("../../api", () => ({
})); }));
// Import after vi.mock so the mock is in place // Import after vi.mock so the mock is in place
import { PluginManager } from "../PluginManager"; import { PluginManager, STATE_COLORS } from "../PluginManager";
import { import {
fetchPlugins, fetchPlugins,
installPlugin, installPlugin,
@@ -886,4 +886,52 @@ describe("PluginManager", () => {
expect(numberInputs.length).toBeGreaterThanOrEqual(3); expect(numberInputs.length).toBeGreaterThanOrEqual(3);
}); });
}); });
describe("toggle switch rendering", () => {
it("renders enabled toggle switch inside .toggle-switch label", async () => {
vi.mocked(fetchPlugins).mockResolvedValueOnce([{ ...mockPlugins[0], enabled: true }]);
render(<PluginManager addToast={addToast} />);
await waitFor(() => {
expect(screen.getByText("Test Plugin A")).toBeTruthy();
});
// Find the toggle-switch label
const toggleSwitch = screen.getByText("Test Plugin A").closest(".plugin-item")?.querySelector(".toggle-switch");
expect(toggleSwitch).toBeTruthy();
// Find the checkbox inside
const checkbox = toggleSwitch?.querySelector('input[type="checkbox"]');
expect(checkbox).toBeTruthy();
expect(checkbox?.checked).toBe(true);
});
it("renders disabled toggle switch inside .toggle-switch label", async () => {
vi.mocked(fetchPlugins).mockResolvedValueOnce([{ ...mockPlugins[0], enabled: false }]);
render(<PluginManager addToast={addToast} />);
await waitFor(() => {
expect(screen.getByText("Test Plugin A")).toBeTruthy();
});
// Find the toggle-switch label
const toggleSwitch = screen.getByText("Test Plugin A").closest(".plugin-item")?.querySelector(".toggle-switch");
expect(toggleSwitch).toBeTruthy();
// Find the checkbox inside
const checkbox = toggleSwitch?.querySelector('input[type="checkbox"]');
expect(checkbox).toBeTruthy();
expect(checkbox?.checked).toBe(false);
});
});
describe("STATE_COLORS", () => {
it("contains only CSS variable references with no hardcoded hex values", () => {
const values = Object.values(STATE_COLORS);
const allUseCssVars = values.every((v) => /^var\(--[a-z-]+\)$/.test(v));
expect(allUseCssVars).toBe(true);
});
});
}); });

View File

@@ -122,6 +122,7 @@
--color-error-dark: #da3633; --color-error-dark: #da3633;
--color-muted: #8b949e; --color-muted: #8b949e;
--color-info: #1f6feb; --color-info: #1f6feb;
--color-warning: #ca8a04;
/* Workflow Step type / state colors */ /* Workflow Step type / state colors */
--ws-pre-merge: #3b82f6; --ws-pre-merge: #3b82f6;
@@ -10270,6 +10271,7 @@ html .column.drag-over * {
--color-success: #1a7f37; --color-success: #1a7f37;
--color-error: #cf222e; --color-error: #cf222e;
--color-muted: #6e7781; --color-muted: #6e7781;
--color-warning: #9a6700;
/* Executor status bar state accents (lower intensity for light backgrounds) */ /* Executor status bar state accents (lower intensity for light backgrounds) */
--executor-status-error-bg: color-mix(in srgb, var(--color-error) 6%, transparent); --executor-status-error-bg: color-mix(in srgb, var(--color-error) 6%, transparent);
@@ -29974,7 +29976,7 @@ html .column.drag-over * {
.plugin-settings-form .form-group { .plugin-settings-form .form-group {
padding: 0; padding: 0;
margin-top: 0; margin-bottom: 0;
} }
.plugin-settings-array { .plugin-settings-array {
@@ -30059,6 +30061,46 @@ html .column.drag-over * {
} }
} }
/* Plugin toggle switch */
.toggle-switch {
position: relative;
display: inline-block;
cursor: pointer;
}
.toggle-switch input {
display: none;
}
.toggle-switch .toggle-slider {
width: 36px;
height: 20px;
background: var(--border);
border-radius: var(--radius-md);
transition: background var(--transition-normal);
position: relative;
}
.toggle-switch .toggle-slider::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: var(--card);
border-radius: 50%;
transition: transform var(--transition-normal);
}
.toggle-switch input:checked + .toggle-slider {
background: var(--in-review);
}
.toggle-switch input:checked + .toggle-slider::after {
transform: translateX(16px);
}
/* Roadmaps view */ /* Roadmaps view */
.roadmaps-view { .roadmaps-view {