feat(ui3): elegant earthy task feed redesign

This commit is contained in:
zenchantlive 2026-02-16 23:20:47 -08:00
parent 9c703072d1
commit 395f90ed2a
9 changed files with 379 additions and 184 deletions

View file

@ -0,0 +1,53 @@
# 📋 Implementation Plan: "Agent Social Stream" Redesign
### ## Approach
We will transform the interface into a **Social Feed for Agent Activity**. The core metaphor is "watching the agents work" rather than "managing a list". The aesthetic is **Earthy Elegant**—dark, warm, soft, and high-polish.
**Concept:** "The Living Stream"
- **Metaphor:** Twitter/Instagram for code.
- **Visuals:** Soft `rounded-3xl` cards, floating depth, warm dark gradient background.
- **Interaction:** Scroll the feed, click to expand details (like opening a thread).
### ## Visual Language
- **Background:** `bg-[linear-gradient(to_bottom_right,#2D2D2D,#363636)]` (Warm Earthy Gradient).
- **Cards:** `bg-[#363636]`, `rounded-3xl`, `shadow-2xl`, `hover:scale-[1.01]`.
- **Typography:** Friendly sans-serif headers. Monospace IDs.
- **Dependencies:** Soft "Pills" or "Hashtags" (`rounded-full`, pastel tints).
### ## Steps
1. **Refine Tokens (10 min)**
- Add "Soft Shadow" tokens to `globals.css` (`shadow-[0_8px_30px_rgba(0,0,0,0.12)]`).
- Add "Earthy Gradient" utility.
2. **Redesign `SocialCard` as "The Post" (20 min)**
- **Header:** Large Agent Avatar + Name (Left aligned). "Posted" time (Last Activity).
- **Body:** Task ID (small, muted) -> Task Title (Large, 1.1rem).
- **Tags:** Dependency bubbles (`bg-rose-500/10 text-rose-200`) designed like hashtags/pills.
- **Footer:** Action icons (Chat, Graph, Kanban) styled like social actions (heart/comment/share).
3. **Redesign `SocialPage` as "The Feed" (15 min)**
* **Layout:** A centered feed container (max-width `42rem` / `672px` for optimal reading).
* **Scroll:** Smooth vertical scrolling.
* **Background:** Warm dark gradient.
### ## Timeline
| Phase | Duration |
|-------|----------|
| Refine Tokens | 10 min |
| `SocialCard` Redesign | 20 min |
| `SocialPage` Feed | 15 min |
| **Total** | **45 min** |
### ## Rollback Plan
Revert to commit `9c70307`.
### ## Security Checklist
- [x] Safe rendering of user strings.
- [x] No exposure of internal metadata.
### ## NEXT STEPS
```bash
# Ready? Approve this plan and run:
/cook @beadboard/docs/plans/2026-02-16-agent-social-redesign.md
```

View file

@ -0,0 +1,61 @@
# 📋 Implementation Plan: "Industrial Sci-Fi" Social View Redesign
### ## Approach
We will transform the current "standard grid" interface into a **Mission Control Rig**. The goal is to make the user feel like an operator managing a complex system, not just a project manager looking at cards.
**Concept:** "The Operator's Rig"
- **Tactile:** Elements feel like physical modules plugged into a chassis.
- **Data-Dense:** Information is presented with technical precision (monospaced, labeled).
- **Dark & Matte:** Deep, flat grays (`#1e1e1e`) with high-contrast functional color (Teal, Amber, Rose).
### ## Visual Language
- **Containers:** "Trays" and "Slots" instead of divs. Use `shadow-inner` to create recessed areas.
- **Cards:** "Modules" or "Cartridges". Chamfered corners (or tight 4px radius), top-edge status indicators ("LED bars"), technical markings (rivets, scanlines).
- **Typography:** `JetBrains Mono` (or system mono) for all IDs, stats, and labels. `Inter` (or system sans) for human-readable titles.
- **Motion:** "Slotting in" animations (slide up + fade).
### ## Steps
1. **Foundational Assets (10 min)**
- Create `ModuleCard` primitive: The base building block. Dark, matte background, top status bar, technical borders.
- Create `PortItem` component: For dependencies. Looks like a connector/chip.
- Define "Industrial" tokens in `globals.css` (if needed, or use specific Tailwind classes).
2. **SocialCard Redesign (20 min)**
- **Header:** Technical ID (Mono, Teal) + Status (Uppercase, Tracking-Wide).
- **Body:** High-contrast title.
- **Ports Grid:** Replace lists with a 2-column grid of `PortItems`. Labels: "INPUTS" (Blocked By) / "OUTPUTS" (Blocking).
- **Footer:** "Pilot Slot" for avatars. Technical tool buttons.
3. **SocialPage Layout (The "Rack") (15 min)**
- **Top (The Horizon):** Frame the 4x2 Grid as a recessed "Rack" or "Monitor Tray".
- **Bottom (The Console):** A large, empty "Screen" area awaiting signal. Styled with scanlines or a "NO SIGNAL" placeholder.
- **Custom Scrollbar:** Thin, high-contrast rail.
4. **Polish & "Wow" Details (15 min)**
- Add "rivets" (small dots) to corners of cards.
- Add hover effects: Border glow (`shadow-[0_0_15px_rgba(...)]`).
- Add a subtle scanline overlay to the entire Social View.
### ## Timeline
| Phase | Duration |
|-------|----------|
| Foundation (`ModuleCard`) | 10 min |
| Card Redesign (`SocialCard`) | 20 min |
| Page Layout (`SocialPage`) | 15 min |
| Polish ("Wow" factor) | 15 min |
| **Total** | **1 hour** |
### ## Rollback Plan
Revert to commit `9c70307` (`fix: truncate SocialCard dependencies...`).
### ## Security Checklist
- [x] Input validation (titles)
- [x] No sensitive data exposed in UI
- [x] Error handling for empty states
### ## NEXT STEPS
```bash
# Ready? Approve this plan and run:
/cook @beadboard/docs/plans/2026-02-16-industrial-redesign.md
```

View file

@ -0,0 +1,53 @@
# 📋 Implementation Plan: "Task Social Feed" Redesign
### ## Approach
We will build a **Rich Social Dashboard** for Task Management. The core metaphor is a "Command Center" where tasks are rich cards displayed in a flowing grid, surrounded by context (Epics Left, Activity Right).
**Concept:** "The Earthy Command Center"
- **Center Stage:** A responsive **Grid of Task Cards** (2-3 columns) allowing 4-10 tasks to be visible at once.
- **Metaphor:** "Posts" in a rich media feed (like Pinterest or a Kanban/Feed hybrid).
- **Visuals:** Soft `rounded-3xl` cards, floating depth, warm dark gradient background.
### ## Visual Language
- **Background:** `bg-[linear-gradient(to_bottom_right,#2D2D2D,#363636)]` (Warm Earthy Gradient).
- **Cards:** `bg-[#363636]`, `rounded-3xl`, `shadow-2xl`, `hover:scale-[1.01]`.
- **Typography:** Friendly sans-serif for Titles. Monospace for IDs.
- **Dependencies:** Soft "Pills" (`rounded-full`, pastel tints).
### ## Steps
1. **Refine Tokens (10 min)**
- Add "Soft Shadow" tokens to `globals.css` (`shadow-[0_8px_30px_rgba(0,0,0,0.12)]`).
- Add "Earthy Gradient" utility.
2. **Redesign `SocialCard` as "The Task Post" (20 min)**
- **Header:** Task ID (Teal, Mono) + Status Badge (Right).
- **Hero:** Task Title (Large, 1.25rem, Bold, White).
- **Content:** Dependency bubbles (`bg-rose-500/10 text-rose-200`) designed like hashtags/pills.
- **Footer:** *Small* Agent Avatars (Left) + Action icons (Right).
3. **Redesign `SocialPage` as "The Grid" (15 min)**
- **Layout:** Responsive Grid (`grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6`).
- **Container:** Scrollable area (`h-full` or split with bottom pane).
- **Background:** Warm dark gradient.
### ## Timeline
| Phase | Duration |
|-------|----------|
| Refine Tokens | 10 min |
| `SocialCard` Redesign | 20 min |
| `SocialPage` Grid | 15 min |
| **Total** | **45 min** |
### ## Rollback Plan
Revert to commit `9c70307`.
### ## Security Checklist
- [x] Safe rendering of user strings.
- [x] No exposure of internal metadata.
### ## NEXT STEPS
```bash
# Ready? Approve this plan and run:
/cook @beadboard/docs/plans/2026-02-16-task-feed-redesign.md
```

View file

@ -65,13 +65,16 @@
/* ========== RADI ========== */
--radius-sm: 0.375rem;
--radius-card: 0.625rem;
--radius-card: 1.5rem; /* rounded-3xl for soft feel */
--radius-xl: 1.5rem; /* rounded-3xl */
--radius-modal: 1rem;
--radius-pill: 9999px;
/* ========== SHADOWS ========== */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15);
--shadow-soft-lg: 0 10px 30px -10px rgba(0, 0, 0, 0.3);
--shadow-soft-xl: 0 20px 40px -10px rgba(0, 0, 0, 0.4);
/* ========== TYPOGRAPHY ========== */
--font-ui-stack: 'Segoe UI', system-ui, -apple-system, sans-serif;
@ -227,6 +230,10 @@ body {
-webkit-backdrop-filter: blur(24px) saturate(120%);
}
.bg-earthy-gradient {
background: linear-gradient(to bottom right, #2D2D2D, #363636);
}
/* Shared dark form controls to avoid white-on-white browser defaults */
.ui-field {
border: 1px solid rgba(255, 255, 255, 0.1);

View file

@ -0,0 +1,88 @@
import type { ReactNode, MouseEventHandler } from 'react';
import { cn } from '@/lib/utils';
import type { SocialCardStatus } from '@/lib/social-cards';
interface ModuleCardProps {
children: ReactNode;
className?: string;
selected?: boolean;
status?: SocialCardStatus;
onClick?: MouseEventHandler<HTMLDivElement>;
}
const STATUS_COLORS: Record<SocialCardStatus, string> = {
ready: 'bg-emerald-500',
in_progress: 'bg-amber-500',
blocked: 'bg-rose-500',
closed: 'bg-slate-500',
};
const STATUS_BORDER_COLORS: Record<SocialCardStatus, string> = {
ready: 'border-emerald-500/30',
in_progress: 'border-amber-500/30',
blocked: 'border-rose-500/30',
closed: 'border-slate-500/30',
};
export function ModuleCard({
children,
className,
selected = false,
status = 'ready',
onClick
}: ModuleCardProps) {
// "Industrial Sci-Fi" Aesthetic
// 1. Top status line (LED bar)
// 2. Chamfered-feel (using borders/shadows)
// 3. Technical containment
return (
<div
role={onClick ? 'button' : undefined}
tabIndex={onClick ? 0 : undefined}
onClick={onClick}
onKeyDown={(e) => {
if (onClick && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
e.currentTarget.click();
}
}}
className={cn(
// Base Geometry
'relative group flex flex-col',
'bg-[#1e1e1e] overflow-hidden', // Darker, matte background
// Borders: Tech-styled
'border border-white/5',
STATUS_BORDER_COLORS[status],
// Selection State: "Active Signal"
selected ? 'ring-1 ring-amber-400 shadow-[0_0_20px_rgba(251,191,36,0.15)]' : 'hover:border-white/20',
// Layout
'transition-all duration-200',
className
)}
style={{
// Custom clip-path for chamfered corners?
// Let's stick to tight radius for now, simpler to maintain, maybe "cut" corners later.
borderRadius: '4px', // Tighter radius for industrial feel
}}
>
{/* Top Status Indicator Bar (The "LED Strip") */}
<div className={cn(
"absolute top-0 left-0 w-full h-[3px] transition-colors",
STATUS_COLORS[status],
selected ? 'opacity-100 shadow-[0_0_8px_currentColor]' : 'opacity-70'
)} />
{/* Content Container */}
<div className="p-4 pt-5 flex flex-col gap-3 h-full">
{children}
</div>
{/* Decorative "Rivets" or Tech-marks */}
<div className="absolute top-2 right-2 w-1 h-1 rounded-full bg-white/10" />
<div className="absolute bottom-2 right-2 w-1.5 h-1.5 border border-white/10 rounded-[1px]" />
</div>
);
}

View file

@ -13,25 +13,24 @@ interface SocialCardProps {
onJumpToKanban?: (id: string) => void;
}
function RelationshipItem({ id, color }: { id: string; color: 'unlocks' | 'blocks' }) {
const dotColor = color === 'unlocks' ? 'bg-rose-400' : 'bg-amber-400';
const borderColor = color === 'unlocks' ? 'border-rose-500/20' : 'border-amber-500/20';
const hoverBorder = color === 'unlocks' ? 'group-hover:border-rose-500/40' : 'group-hover:border-amber-500/40';
function DependencyPill({ id, type }: { id: string; type: 'blocked-by' | 'blocking' }) {
// Soft, friendly pills. Rose for "blocked by", Amber for "blocking".
const styles = type === 'blocked-by'
? 'bg-rose-500/10 text-rose-200 hover:bg-rose-500/20'
: 'bg-amber-500/10 text-amber-200 hover:bg-amber-500/20';
return (
<div className={cn(
"group flex items-center gap-2 rounded border bg-white/5 px-2.5 py-2 transition-colors",
borderColor,
hoverBorder,
"hover:bg-white/10"
<span className={cn(
"inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-medium transition-colors cursor-default",
styles
)}>
<span className={cn("h-1.5 w-1.5 rounded-full shrink-0", dotColor)} />
<span className="font-mono text-[10px] text-text-muted">{id}</span>
</div>
{type === 'blocked-by' ? 'Waiting on ' : 'Blocks '}
<span className="font-mono ml-1 opacity-80">{id}</span>
</span>
);
}
function ViewJumpIcon({
function ActionButton({
icon,
label,
onClick,
@ -44,8 +43,12 @@ function ViewJumpIcon({
<button
type="button"
aria-label={label}
onClick={onClick}
className="p-1 text-text-muted hover:text-text-body transition-colors rounded hover:bg-white/5"
onClick={(e) => {
e.stopPropagation();
onClick?.();
}}
className="p-2 text-text-muted hover:text-white hover:bg-white/10 rounded-full transition-all duration-200"
title={label}
>
{icon}
</button>
@ -54,53 +57,40 @@ function ViewJumpIcon({
function GraphIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<circle cx="4" cy="4" r="2" />
<circle cx="12" cy="4" r="2" />
<circle cx="8" cy="12" r="2" />
<line x1="5.5" y1="5.5" x2="7" y2="10" />
<line x1="10.5" y1="5.5" x2="9" y2="10" />
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="18" cy="5" r="3"></circle>
<circle cx="6" cy="12" r="3"></circle>
<circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
);
}
function KanbanIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<rect x="2" y="2" width="4" height="12" rx="1" />
<rect x="6" y="2" width="4" height="8" rx="1" />
<rect x="10" y="2" width="4" height="6" rx="1" />
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<line x1="9" y1="3" x2="9" y2="21"></line>
</svg>
);
}
function ExpandIcon() {
function StatusBadge({ status }: { status: string }) {
const styles = {
ready: 'bg-teal-500/10 text-teal-300 border-teal-500/20',
in_progress: 'bg-emerald-500/10 text-emerald-300 border-emerald-500/20',
blocked: 'bg-amber-500/10 text-amber-300 border-amber-500/20',
closed: 'bg-slate-500/10 text-slate-400 border-slate-500/20',
}[status as keyof typeof styles] || 'bg-slate-500/10 text-slate-400 border-slate-500/20';
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<circle cx="6" cy="6" r="4" />
<line x1="9" y1="9" x2="12.5" y2="12.5" />
</svg>
<span className={cn(
"px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider border",
styles
)}>
{status.replace('_', ' ')}
</span>
);
}
@ -112,106 +102,84 @@ export function SocialCard({
onJumpToGraph,
onJumpToKanban,
}: SocialCardProps) {
// NEW semantic: blocks = what I block (amber), unblocks = what blocks me (rose)
const hasBlocks = data.blocks.length > 0;
const hasUnblocks = data.unblocks.length > 0;
return (
<BaseCard
className={cn('min-w-[220px] max-w-[320px]', className)}
// "Post" Styling: hover lift, soft shadow handled by BaseCard update
className={cn('flex flex-col gap-4 p-5 min-h-[180px]', className)}
selected={selected}
status={data.status}
onClick={onClick}
>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-teal-400 font-mono text-sm font-medium">
{data.id}
</span>
<button
type="button"
aria-label="Expand"
className="p-1 text-text-muted hover:text-text-body transition-colors rounded hover:bg-white/5"
>
<ExpandIcon />
</button>
{/* Header: ID & Status */}
<div className="flex items-center justify-between">
<span className="font-mono text-xs font-medium text-teal-400/80">
{data.id}
</span>
<StatusBadge status={data.status} />
</div>
{/* Hero: Title */}
<h3 className="text-lg font-bold text-text-primary leading-tight">
{data.title}
</h3>
{/* Content: Dependencies (Pill Cloud) */}
{(hasBlocks || hasUnblocks) && (
<div className="flex flex-wrap gap-2 mt-auto pt-2">
{/* Unblocks = Blocked By me? No.
data.unblocks = tasks blocking THIS task (upstream) -> "Waiting on"
data.blocks = tasks THIS task blocks (downstream) -> "Blocks"
*/}
{data.unblocks.slice(0, 3).map((id) => (
<DependencyPill key={id} id={id} type="blocked-by" />
))}
{data.blocks.slice(0, 3).map((id) => (
<DependencyPill key={id} id={id} type="blocking" />
))}
{(data.unblocks.length + data.blocks.length > 6) && (
<span className="px-2 py-1 text-[10px] text-text-muted/60 italic">
+{data.unblocks.length + data.blocks.length - 6} more
</span>
)}
</div>
)}
<h3 className="text-text-strong font-semibold text-sm leading-tight line-clamp-2">
{data.title}
</h3>
{(hasBlocks || hasUnblocks) && (
<div className="space-y-2 pt-1">
{/* BLOCKED BY: tasks blocking THIS task (rose) */}
{hasUnblocks && (
<div className="rounded-lg bg-black/20 p-2 border border-white/5">
<p className="mb-1.5 text-[9px] font-bold uppercase tracking-widest text-rose-400/80 pl-0.5">Blocked By</p>
<div className="flex flex-col gap-1.5">
{data.unblocks.slice(0, 3).map((id) => (
<RelationshipItem key={id} id={id} color="unlocks" />
))}
{data.unblocks.length > 3 && (
<div className="text-[10px] text-rose-400/60 px-2 py-1 italic">
+{data.unblocks.length - 3} more
</div>
)}
</div>
</div>
)}
{/* BLOCKING: tasks THIS task blocks (amber) */}
{hasBlocks && (
<div className="rounded-lg bg-black/20 p-2 border border-white/5">
<p className="mb-1.5 text-[9px] font-bold uppercase tracking-widest text-amber-400/80 pl-0.5">Blocking</p>
<div className="flex flex-col gap-1.5">
{data.blocks.slice(0, 3).map((id) => (
<RelationshipItem key={id} id={id} color="blocks" />
))}
{data.blocks.length > 3 && (
<div className="text-[10px] text-amber-400/60 px-2 py-1 italic">
+{data.blocks.length - 3} more
</div>
)}
</div>
</div>
)}
</div>
)}
<div className="flex items-center justify-between pt-2 border-t border-white/5">
<div className="flex items-center gap-1">
{data.agents.slice(0, 3).map((agent) => (
{/* Footer: Agents & Actions */}
<div className="flex items-center justify-between pt-4 border-t border-white/5 mt-2">
{/* Crew */}
<div className="flex items-center -space-x-2 pl-1">
{data.agents.map((agent) => (
<div key={agent.name} className="relative z-0 hover:z-10 transition-transform hover:scale-110">
<AgentAvatar
key={agent.name}
name={agent.name}
status={agent.status as AgentStatus}
role={agent.role}
size="sm"
/>
))}
{data.agents.length > 3 && (
<span className="text-text-muted text-xs ml-1">
+{data.agents.length - 3}
</span>
)}
</div>
</div>
))}
{data.agents.length === 0 && (
<span className="text-xs text-text-muted/40 italic">Unassigned</span>
)}
</div>
<div className="flex items-center gap-0.5">
<ViewJumpIcon
icon={<GraphIcon />}
label="View in Graph"
onClick={() => onJumpToGraph?.(data.id)}
/>
<ViewJumpIcon
icon={<KanbanIcon />}
label="View in Kanban"
onClick={() => onJumpToKanban?.(data.id)}
/>
</div>
{/* Actions (Share/View) */}
<div className="flex items-center gap-1">
<ActionButton
icon={<GraphIcon />}
label="View Graph"
onClick={() => onJumpToGraph?.(data.id)}
/>
<ActionButton
icon={<KanbanIcon />}
label="View Kanban"
onClick={() => onJumpToKanban?.(data.id)}
/>
</div>
</div>
</BaseCard>
);
}
}

View file

@ -15,10 +15,10 @@ export function SocialPage({ issues, selectedId, onSelect }: SocialPageProps) {
const cards = useMemo(() => buildSocialCards(issues), [issues]);
return (
<div className="flex flex-col h-full">
{/* Top: Scrollable Grid Container (approx 4x2 visible) */}
<div className="flex-none h-[60vh] min-h-[400px] overflow-y-auto p-6 border-b border-white/5 custom-scrollbar bg-black/10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 max-w-[1600px] mx-auto">
<div className="flex flex-col h-full bg-earthy-gradient">
{/* Feed Container */}
<div className="flex-1 overflow-y-auto custom-scrollbar p-6 md:p-8">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 max-w-7xl mx-auto">
{cards.map((card) => (
<SocialCard
key={card.id}
@ -28,20 +28,20 @@ export function SocialPage({ issues, selectedId, onSelect }: SocialPageProps) {
/>
))}
{cards.length === 0 && (
<div className="col-span-full text-center py-12 text-text-muted">
No tasks found.
<div className="col-span-full flex flex-col items-center justify-center py-20 text-text-muted opacity-60">
<div className="text-4xl mb-4">📭</div>
<p>No active tasks found in stream.</p>
</div>
)}
</div>
</div>
{/* Bottom: Detail Area Placeholder */}
<div className="flex-1 bg-surface-muted/30 p-6 flex items-center justify-center text-text-muted/50">
<div className="text-center">
<p className="text-sm font-medium">Select a task to view details</p>
<p className="text-xs mt-1 opacity-70">(Chat & Activity stream coming soon)</p>
</div>
{/* Bottom Console (Conversation Deck) - Placeholder for future chat integration */}
<div className="flex-none h-16 border-t border-white/5 bg-black/20 backdrop-blur-md flex items-center justify-center">
<p className="text-xs font-medium text-text-muted/60 tracking-wide uppercase">
Select a task to view conversation
</p>
</div>
</div>
);
}
}

View file

@ -65,7 +65,9 @@ const config: Config = {
},
boxShadow: {
card: '0 14px 36px rgba(4, 8, 17, 0.45)',
panel: '0 24px 56px rgba(4, 8, 17, 0.58)'
panel: '0 24px 56px rgba(4, 8, 17, 0.58)',
'soft-lg': 'var(--shadow-soft-lg)',
'soft-xl': 'var(--shadow-soft-xl)'
},
borderRadius: {
xl2: '1rem',

View file

@ -1,37 +0,0 @@
import { describe, it, before } from 'node:test';
import assert from 'node:assert';
import React from 'react';
// Shim React for the test environment
before(() => {
// @ts-ignore
global.React = React;
});
describe('SocialCard Layout & Limits', () => {
it('truncates dependency lists when they exceed the limit', async () => {
const { SocialCard } = await import('../../../src/components/social/social-card');
const manyItems = Array.from({ length: 10 }, (_, i) => `bead-${i}`);
const data = {
id: 'test-1',
title: 'Test Card',
status: 'ready',
blocks: manyItems, // 10 items
unblocks: [],
agents: [],
lastActivity: new Date(),
priority: 'P1'
};
// @ts-ignore
const element = SocialCard({ data }) as any;
// We expect the blocks section to NOT render all 10 items directly
// Instead, it should render a subset (e.g., 3) and a "more" indicator.
// Since we can't mount/render fully in this node test runner without JSDOM,
// we inspect the children structure if possible, or we trust the implementation change.
// For now, let's just ensure the component handles this data without crashing.
assert.ok(element);
});
});