Files
fusion/packages/dashboard/app/components/RuntimeCardShell.tsx
Fusion 6e65786bc5 fix(plugins): re-export probe symbols + declare plugin deps in dashboard
- Hermes / OpenClaw plugin index.ts now re-export `probeHermesBinary` /
  `probeOpenClawBinary` and their status types so the dashboard's
  `runtime-provider-probes.ts` façade can import them via the public
  package entry instead of deep paths.
- Dashboard `package.json` adds `@fusion-plugin-examples/hermes-runtime`,
  `…/openclaw-runtime`, `…/paperclip-runtime` as workspace deps so
  pnpm symlinks them into `packages/dashboard/node_modules/`. Without
  these, the new probe imports failed with "Cannot find module" during
  `pnpm typecheck`.

This clears 6 of the 9 outstanding typecheck errors. The remaining 3 are
in the in-flight Hermes plugin rewrite (runtime-adapter still imports
from a deleted `./pi-module.js`; the new `index.ts` calls a factory
with the wrong arg type) and should be resolved by the same change set
that landed the rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:17:54 -07:00

163 lines
4.7 KiB
TypeScript

/**
* Shared visual shell for the three runtime-provider settings cards
* (Hermes, OpenClaw, Paperclip).
*
* Renders a consistent header (logo + name + status badge + Learn more link),
* a description block, a slot for connection-mode tabs, the form contents the
* caller passes as children, and a footer with Test/Save/Save & Test buttons
* plus a status toast that surfaces probe + save outcomes.
*/
import { ReactNode } from "react";
import { Loader2 } from "lucide-react";
export type RuntimeStatusKind = "neutral" | "ok" | "err" | "loading";
export interface RuntimeCardShellProps {
/** Name shown next to the logo (e.g. "Hermes"). */
name: string;
/** Optional sub-line under the name (e.g. "by Nous Research"). */
subname?: string;
/** The provider logo. Use a `<ProviderIcon>` or an inline `<img>` for brand SVGs. */
logo: ReactNode;
/** "Learn more" target — official documentation/site. */
learnMoreHref: string;
/** Status kind drives the badge color. */
statusKind: RuntimeStatusKind;
/** Status text (e.g. "✓ Detected v0.8.0"). */
statusText: string;
/** Description shown under the header. */
description: ReactNode;
/** Optional extra header chrome (e.g. tabs). Rendered above the form. */
tabs?: ReactNode;
/** The form fields. */
children: ReactNode;
/** Disabled state for the action row. */
busy: "loading" | "saving" | "testing" | "save-test" | null;
/** Disable Test if some required input is missing. */
canTest?: boolean;
/** Toast under the action row — shown when not null. */
toast?: { kind: "ok" | "err"; message: string } | null;
onTest: () => void;
onSave: () => void;
onSaveAndTest: () => void;
/** Optional extra content right above the footer (e.g. an install hint). */
belowForm?: ReactNode;
/** A test-id forwarded to the root element. */
testId?: string;
}
export function RuntimeCardShell(props: RuntimeCardShellProps) {
const {
name,
subname,
logo,
learnMoreHref,
statusKind,
statusText,
description,
tabs,
children,
busy,
canTest = true,
toast,
onTest,
onSave,
onSaveAndTest,
belowForm,
testId,
} = props;
const statusClass =
statusKind === "ok"
? "runtime-card__status runtime-card__status--ok"
: statusKind === "err"
? "runtime-card__status runtime-card__status--err"
: "runtime-card__status runtime-card__status--neutral";
return (
<div className="runtime-card" data-testid={testId} aria-live="polite">
<header className="runtime-card__header">
<span className="runtime-card__logo">{logo}</span>
<div className="runtime-card__title">
<h3 className="runtime-card__name">{name}</h3>
{subname && (
<small className="runtime-card__cobrand">{subname}</small>
)}
<small className={statusClass}>
{statusKind === "loading" && <Loader2 size={12} className="animate-spin" />}
{statusText}
</small>
</div>
<a
className="runtime-card__learn-more btn btn-sm btn-ghost"
href={learnMoreHref}
target="_blank"
rel="noreferrer"
>
Learn more
</a>
</header>
<p className="runtime-card__description">{description}</p>
{tabs}
<div className="runtime-card__form">{children}</div>
{belowForm}
<footer className="runtime-card__footer">
{toast && (
<span
className={
toast.kind === "ok"
? "runtime-card__toast runtime-card__toast--ok"
: "runtime-card__toast runtime-card__toast--err"
}
role="status"
>
{toast.message}
</span>
)}
<button
type="button"
className="btn btn-sm"
onClick={onTest}
disabled={busy !== null || !canTest}
>
{busy === "testing" ? (
<>
<Loader2 size={12} className="animate-spin" /> Testing
</>
) : (
"Test"
)}
</button>
<button
type="button"
className="btn btn-sm"
onClick={onSave}
disabled={busy !== null}
>
{busy === "saving" ? "Saving…" : "Save"}
</button>
<button
type="button"
className="btn btn-primary btn-sm"
onClick={onSaveAndTest}
disabled={busy !== null || !canTest}
>
{busy === "save-test" ? (
<>
<Loader2 size={12} className="animate-spin" /> Saving
</>
) : (
"Save & Test"
)}
</button>
</footer>
</div>
);
}