From 395f90ed2a818fa8969f0f75dc3493a610f74ae4 Mon Sep 17 00:00:00 2001 From: zenchantlive Date: Mon, 16 Feb 2026 23:20:47 -0800 Subject: [PATCH] feat(ui3): elegant earthy task feed redesign --- .../plans/2026-02-16-agent-social-redesign.md | 53 ++++ docs/plans/2026-02-16-industrial-redesign.md | 61 +++++ docs/plans/2026-02-16-task-feed-redesign.md | 53 ++++ src/app/globals.css | 9 +- src/components/shared/module-card.tsx | 88 +++++++ src/components/social/social-card.tsx | 232 ++++++++---------- src/components/social/social-page.tsx | 26 +- tailwind.config.ts | 4 +- .../social/social-card-limits.test.tsx | 37 --- 9 files changed, 379 insertions(+), 184 deletions(-) create mode 100644 docs/plans/2026-02-16-agent-social-redesign.md create mode 100644 docs/plans/2026-02-16-industrial-redesign.md create mode 100644 docs/plans/2026-02-16-task-feed-redesign.md create mode 100644 src/components/shared/module-card.tsx delete mode 100644 tests/components/social/social-card-limits.test.tsx diff --git a/docs/plans/2026-02-16-agent-social-redesign.md b/docs/plans/2026-02-16-agent-social-redesign.md new file mode 100644 index 0000000..d6e6c55 --- /dev/null +++ b/docs/plans/2026-02-16-agent-social-redesign.md @@ -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 +``` diff --git a/docs/plans/2026-02-16-industrial-redesign.md b/docs/plans/2026-02-16-industrial-redesign.md new file mode 100644 index 0000000..f5cfa53 --- /dev/null +++ b/docs/plans/2026-02-16-industrial-redesign.md @@ -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 +``` diff --git a/docs/plans/2026-02-16-task-feed-redesign.md b/docs/plans/2026-02-16-task-feed-redesign.md new file mode 100644 index 0000000..e6d64ce --- /dev/null +++ b/docs/plans/2026-02-16-task-feed-redesign.md @@ -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 +``` \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 52be468..e857177 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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); diff --git a/src/components/shared/module-card.tsx b/src/components/shared/module-card.tsx new file mode 100644 index 0000000..77ea5c6 --- /dev/null +++ b/src/components/shared/module-card.tsx @@ -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; +} + +const STATUS_COLORS: Record = { + ready: 'bg-emerald-500', + in_progress: 'bg-amber-500', + blocked: 'bg-rose-500', + closed: 'bg-slate-500', +}; + +const STATUS_BORDER_COLORS: Record = { + 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 ( +
{ + 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") */} +
+ + {/* Content Container */} +
+ {children} +
+ + {/* Decorative "Rivets" or Tech-marks */} +
+
+
+ ); +} diff --git a/src/components/social/social-card.tsx b/src/components/social/social-card.tsx index a450a5a..049b953 100644 --- a/src/components/social/social-card.tsx +++ b/src/components/social/social-card.tsx @@ -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 ( -
- - {id} -
+ {type === 'blocked-by' ? 'Waiting on ' : 'Blocks '} + {id} + ); } -function ViewJumpIcon({ +function ActionButton({ icon, label, onClick, @@ -44,8 +43,12 @@ function ViewJumpIcon({ @@ -54,53 +57,40 @@ function ViewJumpIcon({ function GraphIcon() { return ( - - - - - - + + + + + + ); } function KanbanIcon() { return ( - - - - + + + ); } -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 ( - - - - + + {status.replace('_', ' ')} + ); } @@ -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 ( -
-
- - {data.id} - - + {/* Header: ID & Status */} +
+ + {data.id} + + +
+ + {/* Hero: Title */} +

+ {data.title} +

+ + {/* Content: Dependencies (Pill Cloud) */} + {(hasBlocks || hasUnblocks) && ( +
+ {/* 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) => ( + + ))} + {data.blocks.slice(0, 3).map((id) => ( + + ))} + {(data.unblocks.length + data.blocks.length > 6) && ( + + +{data.unblocks.length + data.blocks.length - 6} more + + )}
+ )} -

- {data.title} -

- - {(hasBlocks || hasUnblocks) && ( -
- {/* BLOCKED BY: tasks blocking THIS task (rose) */} - {hasUnblocks && ( -
-

Blocked By

-
- {data.unblocks.slice(0, 3).map((id) => ( - - ))} - {data.unblocks.length > 3 && ( -
- +{data.unblocks.length - 3} more -
- )} -
-
- )} - - {/* BLOCKING: tasks THIS task blocks (amber) */} - {hasBlocks && ( -
-

Blocking

-
- {data.blocks.slice(0, 3).map((id) => ( - - ))} - {data.blocks.length > 3 && ( -
- +{data.blocks.length - 3} more -
- )} -
-
- )} -
- )} - - -
-
- {data.agents.slice(0, 3).map((agent) => ( + {/* Footer: Agents & Actions */} +
+ {/* Crew */} +
+ {data.agents.map((agent) => ( +
- ))} - {data.agents.length > 3 && ( - - +{data.agents.length - 3} - - )} -
+
+ ))} + {data.agents.length === 0 && ( + Unassigned + )} +
-
- } - label="View in Graph" - onClick={() => onJumpToGraph?.(data.id)} - /> - } - label="View in Kanban" - onClick={() => onJumpToKanban?.(data.id)} - /> -
+ {/* Actions (Share/View) */} +
+ } + label="View Graph" + onClick={() => onJumpToGraph?.(data.id)} + /> + } + label="View Kanban" + onClick={() => onJumpToKanban?.(data.id)} + />
); -} +} \ No newline at end of file diff --git a/src/components/social/social-page.tsx b/src/components/social/social-page.tsx index 36283b7..aa681f0 100644 --- a/src/components/social/social-page.tsx +++ b/src/components/social/social-page.tsx @@ -15,10 +15,10 @@ export function SocialPage({ issues, selectedId, onSelect }: SocialPageProps) { const cards = useMemo(() => buildSocialCards(issues), [issues]); return ( -
- {/* Top: Scrollable Grid Container (approx 4x2 visible) */} -
-
+
+ {/* Feed Container */} +
+
{cards.map((card) => ( ))} {cards.length === 0 && ( -
- No tasks found. +
+
πŸ“­
+

No active tasks found in stream.

)}
- {/* Bottom: Detail Area Placeholder */} -
-
-

Select a task to view details

-

(Chat & Activity stream coming soon)

-
+ {/* Bottom Console (Conversation Deck) - Placeholder for future chat integration */} +
+

+ Select a task to view conversation +

); -} +} \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index 4ac1367..f71ace0 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -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', diff --git a/tests/components/social/social-card-limits.test.tsx b/tests/components/social/social-card-limits.test.tsx deleted file mode 100644 index d83dd5c..0000000 --- a/tests/components/social/social-card-limits.test.tsx +++ /dev/null @@ -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); - }); -});