diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardShortcuts.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardShortcuts.ts index b365e5ef..ed5f0291 100644 --- a/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardShortcuts.ts +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardShortcuts.ts @@ -91,6 +91,21 @@ export function useDashboardShortcuts({ return () => window.removeEventListener('keydown', handleDelete); }, [selection, dispatch]); + // Cmd/Ctrl+A selects every card so it can be deleted in one go. Skipped + // inside text fields so Cmd+A there still selects text, not cards. + useEffect(() => { + const handleSelectAll = (e: KeyboardEvent) => { + if (!isActive) return; + if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== 'a') return; + const tag = (e.target as HTMLElement)?.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || (e.target as HTMLElement)?.isContentEditable) return; + e.preventDefault(); + selection.selectAll(); + }; + window.addEventListener('keydown', handleSelectAll); + return () => window.removeEventListener('keydown', handleSelectAll); + }, [selection, isActive]); + // Cmd+F to open card search palette useEffect(() => { const handleSearch = (e: KeyboardEvent) => { diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts index b47cc5a8..b5ad9d64 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts @@ -70,6 +70,18 @@ export function useDashboardSelection( const deselectAll = useCallback(() => setSelectedIds(new Map()), []); + // Cmd/Ctrl+A: select every card on the canvas so the user can wipe the + // board in one keystroke. Mirrors the per-type id keys the marquee uses. + const selectAll = useCallback(() => { + const next = new Map(); + for (const card of Object.values(cards)) next.set(card.session_id, 'agent'); + for (const vc of Object.values(viewCards)) next.set(vc.output_id, 'view'); + for (const bc of Object.values(browserCards)) next.set(bc.browser_id, 'browser'); + for (const n of Object.values(notes)) next.set(n.note_id, 'note'); + for (const wc of Object.values(workflowCards)) next.set(wc.workflow_id, 'workflow'); + setSelectedIds(next); + }, [cards, viewCards, browserCards, notes, workflowCards]); + const selectCard = useCallback( (id: string, type: CardType, shiftKey: boolean) => { setSelectedIds((prev) => { @@ -285,6 +297,7 @@ export function useDashboardSelection( isSelected, selectCard, deselectAll, + selectAll, handleCanvasMouseDown, handleCanvasMouseMove, handleCanvasMouseUp, diff --git a/frontend/src/app/pages/Workflows/SchedulingView.tsx b/frontend/src/app/pages/Workflows/SchedulingView.tsx index 1473421c..0be09c03 100644 --- a/frontend/src/app/pages/Workflows/SchedulingView.tsx +++ b/frontend/src/app/pages/Workflows/SchedulingView.tsx @@ -133,9 +133,10 @@ export default function SchedulingView({ workflow, steps }: Props) { }, [pending, dispatch, workflow.id, workflow.updated_at]); return ( - + {/* Inline header replacement. Image #49: subtitle on LEFT, Cancel - on RIGHT. The default History/Run row is hidden for 'scheduling'. */} + on RIGHT. Cancel matches the subtitle's weight/size/color so the + row reads as peers, not a heavy CTA; it just reddens on hover. */} @@ -143,13 +144,12 @@ export default function SchedulingView({ workflow, steps }: Props) { onClick={onCancel} role="button" sx={{ - display: 'inline-flex', alignItems: 'center', gap: 0.45, - fontSize: '0.82rem', fontWeight: 600, - color: c.text.secondary, cursor: 'pointer', - px: 0.75, py: 0.5, + display: 'inline-flex', alignItems: 'center', gap: 0.4, + fontSize: '0.82rem', fontWeight: 500, + color: c.text.muted, cursor: 'pointer', '&:hover': { color: c.status.error }, }}> - + Cancel task scheduling @@ -160,7 +160,6 @@ export default function SchedulingView({ workflow, steps }: Props) { {error && ( {error} )} - {/* Real ChatInput (same one the toolbar / agent chat use) so the composer matches Image #54 / #64 exactly: live model picker, mode picker, thinking level, paperclip + mic, the works. We diff --git a/frontend/src/app/pages/Workflows/WorkflowCard.tsx b/frontend/src/app/pages/Workflows/WorkflowCard.tsx index 87b0266a..de09c970 100644 --- a/frontend/src/app/pages/Workflows/WorkflowCard.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowCard.tsx @@ -463,14 +463,14 @@ const WorkflowCard: React.FC = ({ placeholder="New workflow" onChange={(e) => dispatch(updateWorkflowCard({ workflowId, patch: { draft: { ...(card?.draft || {}), title: e.target.value } } }))} sx={{ - flex: 1, fontWeight: 700, fontSize: '1rem', color: c.text.primary, + flex: 1, fontWeight: 600, fontSize: '0.95rem', color: c.text.primary, letterSpacing: '-0.005em', '& input::placeholder': { color: c.text.muted, opacity: 1 }, }} /> ) : ( <> - + {title} diff --git a/frontend/src/shared/state/dashboardLayoutSlice.ts b/frontend/src/shared/state/dashboardLayoutSlice.ts index c3482455..400f9290 100644 --- a/frontend/src/shared/state/dashboardLayoutSlice.ts +++ b/frontend/src/shared/state/dashboardLayoutSlice.ts @@ -18,7 +18,7 @@ export const DEFAULT_VIEW_CARD_W = 1280; export const DEFAULT_VIEW_CARD_H = 800; export const DEFAULT_BROWSER_CARD_W = 1280; export const DEFAULT_BROWSER_CARD_H = 800; -export const DEFAULT_WORKFLOW_CARD_W = 440; +export const DEFAULT_WORKFLOW_CARD_W = 480; export const DEFAULT_WORKFLOW_CARD_H = 520; export const DEFAULT_WORKFLOWS_HUB_W = 1200; export const DEFAULT_WORKFLOWS_HUB_H = 640;