diff --git a/frontend/src/app/components/share/IncludesList.tsx b/frontend/src/app/components/share/IncludesList.tsx index 00057d7f..4494c16c 100644 --- a/frontend/src/app/components/share/IncludesList.tsx +++ b/frontend/src/app/components/share/IncludesList.tsx @@ -1,9 +1,13 @@ // The "what's inside this bundle" panel, shared by the Share and Import modals: // the root entity, the dependencies pulled in with it, and any environment -// requirements (an Action the importer must enable themselves). -import React from 'react'; +// requirements (an Action the importer must enable themselves). Long bundles +// (a dashboard pulls in every agent) read as a wall, so the contents collapse +// to a one-line count by default and expand on demand. Requirements always +// show, they're the part the importer has to act on. +import React, { useState } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; @@ -18,8 +22,23 @@ const KIND_LABEL: Record = { session: 'Agent', }; +const pluralize = (label: string, n: number): string => (n === 1 ? label : `${label}s`); + const IncludesList: React.FC<{ summary: BundleSummary }> = ({ summary }) => { const c = useClaudeTokens(); + const [expanded, setExpanded] = useState(false); + const includes = summary.includes; + + // One quiet line: "9 agents · 1 app", in the bundle's own type order. + const order: string[] = []; + const byType = new Map(); + for (const it of includes) { + if (!byType.has(it.type)) order.push(it.type); + byType.set(it.type, (byType.get(it.type) || 0) + 1); + } + const countLine = order + .map((t) => `${byType.get(t)} ${pluralize((KIND_LABEL[t] || t).toLowerCase(), byType.get(t) || 0)}`) + .join(' · '); const Row: React.FC<{ tag: string; name: string; detail?: string; faded?: boolean }> = ({ tag, @@ -27,7 +46,7 @@ const IncludesList: React.FC<{ summary: BundleSummary }> = ({ summary }) => { detail, faded, }) => ( - + = ({ summary }) => { }} > - {summary.includes.map((it, i) => ( - - ))} + + {includes.length > 0 && !expanded && ( + setExpanded(true)} + sx={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: 1, + py: 0.55, + cursor: 'pointer', + '&:hover .show-toggle': { color: c.accent.primary }, + }} + > + {countLine} + + Show + + + + )} + + {includes.length > 0 && expanded && ( + <> + {includes.map((it, i) => ( + + ))} + setExpanded(false)} + sx={{ py: 0.4, cursor: 'pointer', color: c.text.tertiary, '&:hover': { color: c.accent.primary } }} + > + Hide + + + )} + {summary.requirements.length > 0 && ( {summary.requirements.map((r, i) => ( diff --git a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx index 7abc7504..adc22f07 100644 --- a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx @@ -67,6 +67,44 @@ interface Props { onBringToFront?: (id: string, type: 'agent' | 'view' | 'browser') => void; } +// The app card's loading state while its runtime spins up. One soft pulse, calm +// copy, and an honest hint only after 9s, a freshly-imported app installs its +// deps on first open, which is the slow case worth explaining instead of leaving +// the user staring at a dead screen. +const BootingBody: React.FC = () => { + const c = useClaudeTokens(); + const [slow, setSlow] = useState(false); + useEffect(() => { + const t = setTimeout(() => setSlow(true), 9000); + return () => clearTimeout(t); + }, []); + return ( + + + Starting preview + + + First run sets the app up, this can take a moment. + + + + ); +}; + const DashboardViewCard: React.FC = ({ output, cardX, cardY, cardWidth, cardHeight, zoom = 1, panX = 0, panY = 0, cmdHeld = false, isSelected = false, isHighlighted = false, multiDragDelta, onCardSelect, onDragStart, onDragMove, onDragEnd, @@ -681,24 +719,7 @@ const DashboardOutputPreview: React.FC<{ } if (isBooting) { - return ( - - Starting preview… - - ); + return ; } return (