diff --git a/apps/agent-forge/src/screens/agentList/AgentListScreen.tsx b/apps/agent-forge/src/screens/agentList/AgentListScreen.tsx
index 50b1a701..8855e9c9 100644
--- a/apps/agent-forge/src/screens/agentList/AgentListScreen.tsx
+++ b/apps/agent-forge/src/screens/agentList/AgentListScreen.tsx
@@ -7,15 +7,15 @@
* `useAgentList`.
*/
import { useCallback, useMemo } from 'react';
-import { Bot, CircleAlert, SearchX } from 'lucide-react';
+import { Bot, SearchX } from 'lucide-react';
import type { AgentListItem } from '../../../shared/wireTypes';
import { navigate } from '../../router/router';
import { ScreenHeader } from '../../shell/ScreenHeader';
-import { Card } from '../../ui/Card';
import { EmptyState } from '../../ui/EmptyState';
import { FilterBar, type FacetDef } from '../../ui/FilterBar';
import { Skeleton } from '../../ui/Skeleton';
import { facet } from '../listFilters';
+import { LoadErrorCard, LoadingGrid } from '../listStates';
import { agentTabPath } from '../agents/agentTabs';
import { AgentCard } from './AgentCard';
import { filterAgents, labelOptions, useAgentListFilter, type AgentListFilter } from './agentListUrl';
@@ -24,8 +24,6 @@ import { useNewAgentActions, type NewAgentActions } from './newAgent';
import { useAgentList, type AgentListState } from './useAgentList';
import './agentList.css';
-const SKELETON_CARDS = 6;
-
const plural = (n: number): string => (n === 1 ? 'agent' : 'agents');
/** One shimmer card standing in for an agent while the list loads. */
@@ -44,38 +42,6 @@ function SkeletonCard() {
);
}
-/** The first-load shimmer grid: skeleton cards plus one hidden status line. */
-function LoadingGrid() {
- return (
-
- -
- Loading agents
-
- {Array.from({ length: SKELETON_CARDS }, (_, index) => (
-
- ))}
-
- );
-}
-
-/** The error card with Retry (`refresh`). */
-function ErrorCard({ error, onRetry }: { error: string; onRetry(): void }) {
- return (
-
-
- Retry
-
- }
- />
-
- );
-}
-
/** The loaded grid: one AgentCard per visible item. */
function AgentGrid({
items,
@@ -116,9 +82,11 @@ interface ListBodyProps {
/** Exactly one of: error card, loading grid, empty state, no-match state, card grid. */
function AgentListBody({ list, items, visible, newAgent, onClearFilters, onOpen, onToggle }: ListBodyProps) {
if (list.error !== undefined && list.items === undefined) {
- return
;
+ return
;
+ }
+ if (list.items === undefined) {
+ return
;
}
- if (list.items === undefined) return
;
if (items.length === 0) {
return (
+
+ {status}
+
+ {Array.from({ length: SKELETON_CARDS }, (_, index) => (
+
+ ))}
+
+ );
+}
+
+/** A card list's load-error card with Retry. */
+export function LoadErrorCard({ title, error, onRetry }: { title: string; error: string; onRetry(): void }) {
+ return (
+
+
+ Retry
+
+ }
+ />
+
+ );
+}
diff --git a/apps/agent-forge/src/screens/templates/TemplateCard.tsx b/apps/agent-forge/src/screens/templates/TemplateCard.tsx
index a3416c7b..6fe59206 100644
--- a/apps/agent-forge/src/screens/templates/TemplateCard.tsx
+++ b/apps/agent-forge/src/screens/templates/TemplateCard.tsx
@@ -5,6 +5,7 @@
*/
import { LayoutTemplate } from 'lucide-react';
import type { TemplateSummary } from '../../../shared/wireTypes';
+import '../../ui/tileCard.css';
import { requiresLine } from './templatesModel';
/** At most this many tags show on a card. */
@@ -14,7 +15,7 @@ export function TemplateCard({ summary, onOpen }: { summary: TemplateSummary; on
const titleId = `template-card-title-${summary.id}`;
const needs = requiresLine(summary.requires);
return (
-
+
diff --git a/apps/agent-forge/src/screens/templates/TemplateSheet.tsx b/apps/agent-forge/src/screens/templates/TemplateSheet.tsx
index a90ab21d..2da10537 100644
--- a/apps/agent-forge/src/screens/templates/TemplateSheet.tsx
+++ b/apps/agent-forge/src/screens/templates/TemplateSheet.tsx
@@ -33,11 +33,9 @@ export interface TemplateSheetProps {
onClose(): void;
}
-export function TemplateSheet({ templateId, summary, onClose }: TemplateSheetProps) {
- const toast = useToast();
+function useTemplateDetail(templateId: string | null) {
const [detailState, setDetailState] = useState({ for: '', status: 'loading' });
const [reload, setReload] = useState(0);
- const [creating, setCreating] = useState(false);
useEffect(() => {
if (templateId === null) return;
@@ -62,14 +60,19 @@ export function TemplateSheet({ templateId, summary, onClose }: TemplateSheetPro
// Until the fetch for THIS id lands, the sheet shows its loading state - a
// previous template's detail never leaks into the next sheet.
const current = templateId !== null && detailState.for === templateId ? detailState : undefined;
- const status = current?.status ?? 'loading';
- const detail = current?.status === 'loaded' ? current.detail : undefined;
- const detailError = current?.status === 'error' ? current.message : undefined;
+ return {
+ status: current?.status ?? 'loading',
+ detail: current?.status === 'loaded' ? current.detail : undefined,
+ detailError: current?.status === 'error' ? current.message : undefined,
+ retry: () => setReload((n) => n + 1),
+ };
+}
- const title = detail?.summary.title ?? summary?.title ?? templateId ?? '';
- const category = detail?.summary.category ?? summary?.category;
+function useCreateFromTemplate(templateId: string | null, title: string) {
+ const toast = useToast();
+ const [creating, setCreating] = useState(false);
- const useTemplate = () => {
+ const create = () => {
if (templateId === null || creating) return;
const id = templateId;
setCreating(true);
@@ -83,7 +86,42 @@ export function TemplateSheet({ templateId, summary, onClose }: TemplateSheetPro
.finally(() => setCreating(false));
};
- const requires = detail?.summary.requires ?? [];
+ return { creating, create };
+}
+
+function TemplateSheetDetail({ detail }: { detail: TemplateDetail }) {
+ const requires = detail.summary.requires ?? [];
+ return (
+ <>
+
+ What it needs
+ {requires.length === 0 ? (
+ Nothing extra. It runs with your default model.
+ ) : (
+
+ {requires.map((kind) => (
+ -
+
+ {REQUIRE_LABELS[kind]}
+
+ ))}
+
+ )}
+
+
+ About
+ {detail.readme}
+
+ >
+ );
+}
+
+export function TemplateSheet({ templateId, summary, onClose }: TemplateSheetProps) {
+ const { status, detail, detailError, retry } = useTemplateDetail(templateId);
+
+ const title = detail?.summary.title ?? summary?.title ?? templateId ?? '';
+ const category = detail?.summary.category ?? summary?.category;
+ const { creating, create } = useCreateFromTemplate(templateId, title);
return (
Cancel
-
)}
- {detail !== undefined && (
- <>
-