## Summary Adds a bundled **Quality** plugin (`fusion-plugin-quality`) that makes task QA easier and more visual: - **Task QA tab** (action-first): preview/test server for the task worktree, allowlisted test runs, report viewer, screenshots CTA, suggested test cases, CI handoff - **Quality hub** (left sidebar): project-wide run history and preset launches - Host **task-detail slot context** (`taskId`, worktree, `projectId`) so plugin tabs can scope correctly - `superviseSpawn` re-exported on the plugin packaging shim for published plugins - Plan: `docs/plans/2026-07-14-001-feat-quality-plugin-plan.md` ## Design constraints - Does **not** replace the merge gate — advisory orchestration only - Composes Dev Server process patterns and artifact registry (no second browser stack) - Never free-form shell; never port 4040 - Full-suite requires explicit confirm ## Test plan - [x] `pnpm --filter @fusion-plugin-examples/quality test` (15 tests) - [x] PluginSlot unit tests still pass - [ ] Enable Quality plugin in dashboard Settings → Built-in Plugins - [ ] Open Task Detail → **QA** tab with a worktree; start preview, run verify:fast, generate suggestions - [ ] Open left sidebar **Quality** hub and list runs - [ ] Confirm merge gate / PR checks unchanged ## Residual / follow-up (same plan, later units) - Deeper hub CI (host route) - Full browser-verification toggle UX + agent QA sessions (U7/U9/U10) - Richer screenshots gallery wiring to live artifacts API - Test plans CRUD polish <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the Quality plugin with a project Quality hub and task-focused QA tab. * Added test runs, reports, preview server controls, suggested test cases, and run history. * Added configurable test presets, cancellation, status tracking, and safe command execution. * Added experimental-feature controls for enabling Quality functionality. * Bundled Quality with the CLI and made it available through the plugin manager. * **Documentation** * Added Quality plugin guidance, terminology, configuration details, and implementation planning documentation. * **Bug Fixes** * Improved process supervision so command failures and shutdown timers are handled safely. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
161 lines
5.0 KiB
TypeScript
161 lines
5.0 KiB
TypeScript
import type { ComponentType, ReactNode } from "react";
|
|
import { lazy, Suspense, createElement } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import type { PluginUiSlotEntry } from "../api";
|
|
import { DroidCliProviderCard } from "../components/DroidCliProviderCard";
|
|
|
|
export interface PluginSlotHostActions {
|
|
refreshAuthProviders?: () => void;
|
|
openSettingsSection?: (section: string) => void;
|
|
openModelOnboarding?: () => void;
|
|
}
|
|
|
|
/*
|
|
FNXC:Quality 2026-07-14-21:50:
|
|
Task-detail plugin tabs need host-injected task context (taskId, worktree, projectId).
|
|
Without this bag Task QA cannot scope runs, preview servers, or suggestions.
|
|
*/
|
|
export interface PluginSlotTaskContext {
|
|
taskId?: string;
|
|
worktree?: string;
|
|
projectId?: string;
|
|
title?: string;
|
|
modifiedFiles?: string[];
|
|
}
|
|
|
|
export interface PluginSlotComponentProps {
|
|
entry: PluginUiSlotEntry;
|
|
actions?: PluginSlotHostActions;
|
|
context?: PluginSlotTaskContext;
|
|
taskId?: string;
|
|
worktree?: string;
|
|
projectId?: string;
|
|
}
|
|
|
|
interface PluginSlotRegistration {
|
|
pluginId: string;
|
|
slotId: string;
|
|
componentPath: string;
|
|
component: ComponentType<PluginSlotComponentProps>;
|
|
}
|
|
|
|
function DroidSettingsProviderCard({ actions }: PluginSlotComponentProps): ReactNode {
|
|
return (
|
|
<DroidCliProviderCard
|
|
compact
|
|
authenticated={false}
|
|
onToggled={() => {
|
|
actions?.refreshAuthProviders?.();
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function DroidOnboardingProviderCard({ actions }: PluginSlotComponentProps): ReactNode {
|
|
return (
|
|
<DroidCliProviderCard
|
|
authenticated={false}
|
|
onToggled={() => {
|
|
actions?.refreshAuthProviders?.();
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function DroidOnboardingSetupHelp(): ReactNode {
|
|
const { t } = useTranslation("app");
|
|
return (
|
|
<p className="onboarding-helper-text" data-testid="droid-onboarding-setup-help">
|
|
{t("plugins.droidOnboardingTip", "Tip: Enable Droid CLI to reuse your Factory AI subscription without adding an API key.")}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
function DroidPostOnboardingRecommendation({ actions }: PluginSlotComponentProps): ReactNode {
|
|
const { t } = useTranslation("app");
|
|
return (
|
|
<div className="post-onboarding-recommendations__item" data-testid="droid-post-onboarding-recommendation">
|
|
<span className="post-onboarding-recommendations__item-text">
|
|
<strong>{t("plugins.droidRecommendTitle", "Enable Droid CLI")}</strong>
|
|
<span>{t("plugins.droidRecommendDesc", "Use your local Droid CLI session as an AI provider in Fusion.")}</span>
|
|
</span>
|
|
<button type="button" className="btn btn-sm" onClick={() => actions?.openSettingsSection?.("authentication")}>
|
|
{t("plugins.openAuthentication", "Open Authentication")}
|
|
</button>
|
|
<button type="button" className="btn btn-sm" onClick={() => actions?.openModelOnboarding?.()}>
|
|
{t("plugins.openOnboarding", "Open Onboarding")}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const LazyQualityQaTab = lazy(async () => {
|
|
const mod = await import("@fusion-plugin-examples/quality/qa-tab");
|
|
return { default: mod.QualityQaTabSlot ?? mod.default };
|
|
});
|
|
|
|
function QualityTaskDetailTab(props: PluginSlotComponentProps): ReactNode {
|
|
return (
|
|
<Suspense fallback={null}>
|
|
{createElement(LazyQualityQaTab, {
|
|
entry: props.entry,
|
|
actions: props.actions,
|
|
context: props.context,
|
|
taskId: props.taskId ?? props.context?.taskId,
|
|
worktree: props.worktree ?? props.context?.worktree,
|
|
projectId: props.projectId ?? props.context?.projectId,
|
|
})}
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
const REGISTRY: PluginSlotRegistration[] = [
|
|
{
|
|
pluginId: "fusion-plugin-quality",
|
|
slotId: "task-detail-tab",
|
|
componentPath: "./qa-tab",
|
|
component: QualityTaskDetailTab,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "settings-provider-card",
|
|
componentPath: "./components/settings-provider-card.js",
|
|
component: DroidSettingsProviderCard,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "settings-integration-card",
|
|
componentPath: "./components/settings-integration-card.js",
|
|
component: DroidSettingsProviderCard,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "onboarding-provider-card",
|
|
componentPath: "./components/onboarding-provider-card.js",
|
|
component: DroidOnboardingProviderCard,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "onboarding-setup-help",
|
|
componentPath: "./components/onboarding-setup-help.js",
|
|
component: DroidOnboardingSetupHelp,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "post-onboarding-recommendation",
|
|
componentPath: "./components/post-onboarding-recommendation.js",
|
|
component: DroidPostOnboardingRecommendation,
|
|
},
|
|
];
|
|
|
|
export function resolvePluginSlotComponent(entry: PluginUiSlotEntry): ComponentType<PluginSlotComponentProps> | null {
|
|
const hit = REGISTRY.find(
|
|
(candidate) =>
|
|
candidate.pluginId === entry.pluginId
|
|
&& candidate.slotId === entry.slot.slotId
|
|
&& candidate.componentPath === entry.slot.componentPath,
|
|
);
|
|
|
|
return hit?.component ?? null;
|
|
}
|