checkpoint: pre-split branch cleanup
This commit is contained in:
parent
4c2ae2e5b7
commit
b5db7a7753
276 changed files with 35912 additions and 60119 deletions
1
.agent/skills/shadcn-ui
Symbolic link
1
.agent/skills/shadcn-ui
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,306 +0,0 @@
|
||||||
### shadcn/ui Chart Component - Installation
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add chart
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Basic Usage
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", desktop: 186, mobile: 80 },
|
|
||||||
{ month: "February", desktop: 305, mobile: 200 },
|
|
||||||
{ month: "March", desktop: 237, mobile: 120 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function BarChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
||||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - ChartConfig with Custom Colors
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
You can define custom colors directly in the configuration using hex values or CSS variables.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "#2563eb",
|
|
||||||
theme: {
|
|
||||||
light: "#2563eb",
|
|
||||||
dark: "#60a5fa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - CSS Variables
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Add chart color variables to your globals.css for consistent theming.
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Chart colors */
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.546 0.198 38.228);
|
|
||||||
--chart-4: oklch(0.596 0.151 343.253);
|
|
||||||
--chart-5: oklch(0.546 0.158 49.157);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.698 0.141 24.311);
|
|
||||||
--chart-4: oklch(0.676 0.172 171.196);
|
|
||||||
--chart-5: oklch(0.578 0.192 302.85);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Line Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a line chart with shadcn/ui charts component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
price: {
|
|
||||||
label: "Price",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", price: 186 },
|
|
||||||
{ month: "February", price: 305 },
|
|
||||||
{ month: "March", price: 237 },
|
|
||||||
{ month: "April", price: 203 },
|
|
||||||
{ month: "May", price: 276 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function LineChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
|
|
||||||
<Line
|
|
||||||
dataKey="price"
|
|
||||||
stroke="var(--color-price)"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Area Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating an area chart with gradient fill and legend.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Area, AreaChart, XAxis, YAxis } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
||||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
export function AreaChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<AreaChart data={chartData}>
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} />
|
|
||||||
<Area
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
stroke="var(--color-desktop)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
stroke="var(--color-mobile)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Pie Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a pie/donut chart with shadcn/ui.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Pie, PieChart } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
||||||
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
||||||
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const pieData = [
|
|
||||||
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
|
|
||||||
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
|
|
||||||
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PieChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<PieChart>
|
|
||||||
<Pie
|
|
||||||
data={pieData}
|
|
||||||
dataKey="visitors"
|
|
||||||
nameKey="browser"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
outerRadius={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</PieChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui ChartTooltipContent Props
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
|
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
|
||||||
|------|------|---------|-------------|
|
|
||||||
| `labelKey` | string | "label" | Key for tooltip label |
|
|
||||||
| `nameKey` | string | "name" | Key for tooltip name |
|
|
||||||
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
|
|
||||||
| `hideLabel` | boolean | false | Hide label |
|
|
||||||
| `hideIndicator` | boolean | false | Hide indicator |
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Accessibility
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<BarChart accessibilityLayer data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
- Keyboard arrow key navigation
|
|
||||||
- ARIA labels for chart elements
|
|
||||||
- Screen reader announcements for data values
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Recharts Dependencies
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component requires the following Recharts dependencies to be installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add recharts
|
|
||||||
npm install recharts
|
|
||||||
yarn add recharts
|
|
||||||
```
|
|
||||||
|
|
||||||
Recharts provides the following chart types:
|
|
||||||
- Area, Bar, Line, Pie, Composed
|
|
||||||
- Radar, RadialBar, Scatter
|
|
||||||
- Funnel, Treemap
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# shadcn/ui Learning Guide
|
|
||||||
|
|
||||||
This guide helps you learn shadcn/ui from basics to advanced patterns.
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
### 1. Understanding the Philosophy
|
|
||||||
|
|
||||||
shadcn/ui is different from traditional component libraries:
|
|
||||||
|
|
||||||
- **Copy-paste components**: Components are copied into your project, not installed as packages
|
|
||||||
- **Full customization**: You own the code and can modify it freely
|
|
||||||
- **Built on Radix UI**: Provides accessibility primitives
|
|
||||||
- **Styled with Tailwind**: Uses utility classes for consistent styling
|
|
||||||
|
|
||||||
### 2. Core Concepts to Master
|
|
||||||
|
|
||||||
#### Class Variance Authority (CVA)
|
|
||||||
Most components use CVA for variant management:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"base-classes",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "variant-classes",
|
|
||||||
destructive: "destructive-classes",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "size-classes",
|
|
||||||
sm: "small-classes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cn Utility Function
|
|
||||||
The `cn` function combines classes and resolves conflicts:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation Checklist
|
|
||||||
|
|
||||||
- [ ] Initialize a new project (Next.js, Vite, or Remix)
|
|
||||||
- [ ] Install Tailwind CSS
|
|
||||||
- [ ] Run `npx shadcn@latest init`
|
|
||||||
- [ ] Configure CSS variables
|
|
||||||
- [ ] Install first component: `npx shadcn@latest add button`
|
|
||||||
|
|
||||||
### 4. Essential Components to Learn First
|
|
||||||
|
|
||||||
1. **Button** - Learn variants and sizes
|
|
||||||
2. **Input** - Form inputs with labels
|
|
||||||
3. **Card** - Container components
|
|
||||||
4. **Form** - Form handling with React Hook Form
|
|
||||||
5. **Dialog** - Modal windows
|
|
||||||
6. **Select** - Dropdown selections
|
|
||||||
7. **Toast** - Notifications
|
|
||||||
|
|
||||||
### 5. Common Patterns
|
|
||||||
|
|
||||||
#### Form Pattern
|
|
||||||
Every form follows this structure:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
1. Define Zod schema
|
|
||||||
2. Create form with useForm
|
|
||||||
3. Wrap with Form component
|
|
||||||
4. Add FormField for each input
|
|
||||||
5. Handle submission
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Component Customization Pattern
|
|
||||||
To customize a component:
|
|
||||||
|
|
||||||
1. Copy component to your project
|
|
||||||
2. Modify the variants
|
|
||||||
3. Add new props if needed
|
|
||||||
4. Update types
|
|
||||||
|
|
||||||
### 6. Best Practices
|
|
||||||
|
|
||||||
- Always use TypeScript
|
|
||||||
- Follow the existing component structure
|
|
||||||
- Use semantic HTML when possible
|
|
||||||
- Test with screen readers for accessibility
|
|
||||||
- Keep components small and focused
|
|
||||||
|
|
||||||
### 7. Advanced Topics
|
|
||||||
|
|
||||||
- Creating custom components from scratch
|
|
||||||
- Building complex forms with validation
|
|
||||||
- Implementing dark mode
|
|
||||||
- Optimizing for performance
|
|
||||||
- Testing components
|
|
||||||
|
|
||||||
## Practice Exercises
|
|
||||||
|
|
||||||
### Exercise 1: Basic Setup
|
|
||||||
1. Create a new Next.js project
|
|
||||||
2. Set up shadcn/ui
|
|
||||||
3. Install and customize a Button component
|
|
||||||
4. Add a new variant "gradient"
|
|
||||||
|
|
||||||
### Exercise 2: Form Building
|
|
||||||
1. Create a contact form with:
|
|
||||||
- Name input (required)
|
|
||||||
- Email input (email validation)
|
|
||||||
- Message textarea (min length)
|
|
||||||
- Submit button with loading state
|
|
||||||
|
|
||||||
### Exercise 3: Component Combination
|
|
||||||
1. Build a settings page using:
|
|
||||||
- Card for layout
|
|
||||||
- Sheet for mobile menu
|
|
||||||
- Select for dropdowns
|
|
||||||
- Switch for toggles
|
|
||||||
- Toast for notifications
|
|
||||||
|
|
||||||
### Exercise 4: Custom Component
|
|
||||||
1. Create a custom Badge component
|
|
||||||
2. Support variants: default, secondary, destructive, outline
|
|
||||||
3. Support sizes: sm, default, lg
|
|
||||||
4. Add icon support
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Official Documentation](https://ui.shadcn.com)
|
|
||||||
- [GitHub Repository](https://github.com/shadcn/ui)
|
|
||||||
- [Examples Gallery](https://ui.shadcn.com/examples)
|
|
||||||
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
|
|
||||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,586 +0,0 @@
|
||||||
# shadcn.io Component Library
|
|
||||||
|
|
||||||
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
|
|
||||||
|
|
||||||
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
|
|
||||||
|
|
||||||
## Core Components
|
|
||||||
|
|
||||||
### Terminal Component
|
|
||||||
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Terminal } from "@/components/ui/terminal"
|
|
||||||
|
|
||||||
export default function DemoTerminal() {
|
|
||||||
return (
|
|
||||||
npm install @repo/terminalInstalling dependencies...npm start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dock Component
|
|
||||||
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Dock, DockIcon } from "@/components/ui/dock"
|
|
||||||
import { Home, Settings, User, Mail } from "lucide-react"
|
|
||||||
|
|
||||||
export default function AppDock() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credit Card Component
|
|
||||||
Interactive 3D credit card component with flip animations for payment forms and card displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { CreditCard } from "@/components/ui/credit-card"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function PaymentForm() {
|
|
||||||
const [cardData, setCardData] = useState({
|
|
||||||
number: "4532 1234 5678 9010",
|
|
||||||
holder: "JOHN DOE",
|
|
||||||
expiry: "12/28",
|
|
||||||
cvv: "123"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
console.log("Card flipped:", flipped)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Zoom Component
|
|
||||||
Zoomable image component with smooth modal transitions for image galleries and product displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ImageZoom } from "@/components/ui/image-zoom"
|
|
||||||
|
|
||||||
export default function ProductGallery() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Code Component
|
|
||||||
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { QRCode } from "@/components/ui/qr-code"
|
|
||||||
|
|
||||||
export default function ShareDialog() {
|
|
||||||
const shareUrl = "https://shadcn.io"
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Scan to visit shadcn.io
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Picker Component
|
|
||||||
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ColorPicker } from "@/components/ui/color-picker"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function ThemeCustomizer() {
|
|
||||||
const [color, setColor] = useState("#3b82f6")
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Selected: {color}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chart Components
|
|
||||||
|
|
||||||
### Bar Chart Component
|
|
||||||
Clean bar chart component for data comparison and categorical analysis using Recharts.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { BarChart } from "@/components/ui/bar-chart"
|
|
||||||
|
|
||||||
export default function SalesChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", sales: 4000, revenue: 2400 },
|
|
||||||
{ month: "Feb", sales: 3000, revenue: 1398 },
|
|
||||||
{ month: "Mar", sales: 2000, revenue: 9800 },
|
|
||||||
{ month: "Apr", sales: 2780, revenue: 3908 },
|
|
||||||
{ month: "May", sales: 1890, revenue: 4800 },
|
|
||||||
{ month: "Jun", sales: 2390, revenue: 3800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`$${value.toLocaleString()}`}
|
|
||||||
yAxisWidth={60}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Chart Component
|
|
||||||
Smooth line chart for visualizing trends and time-series data with multiple data series support.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LineChart } from "@/components/ui/line-chart"
|
|
||||||
|
|
||||||
export default function MetricsChart() {
|
|
||||||
const data = [
|
|
||||||
{ date: "2024-01", users: 1200, sessions: 3400 },
|
|
||||||
{ date: "2024-02", users: 1800, sessions: 4200 },
|
|
||||||
{ date: "2024-03", users: 2400, sessions: 5800 },
|
|
||||||
{ date: "2024-04", users: 3100, sessions: 7200 },
|
|
||||||
{ date: "2024-05", users: 3800, sessions: 8900 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Component
|
|
||||||
Donut chart component for displaying proportional data and percentage distributions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { PieChart } from "@/components/ui/pie-chart"
|
|
||||||
|
|
||||||
export default function MarketShareChart() {
|
|
||||||
const data = [
|
|
||||||
{ name: "Product A", value: 400, fill: "#3b82f6" },
|
|
||||||
{ name: "Product B", value: 300, fill: "#10b981" },
|
|
||||||
{ name: "Product C", value: 300, fill: "#f59e0b" },
|
|
||||||
{ name: "Product D", value: 200, fill: "#ef4444" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${entry.name}: ${entry.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Area Chart Component
|
|
||||||
Stacked area chart for visualizing volume changes over time with multiple data series.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AreaChart } from "@/components/ui/area-chart"
|
|
||||||
|
|
||||||
export default function TrafficChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
|
|
||||||
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
|
|
||||||
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
|
|
||||||
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
|
|
||||||
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Radar Chart Component
|
|
||||||
Multi-axis chart for comparing multiple variables across different categories simultaneously.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RadarChart } from "@/components/ui/radar-chart"
|
|
||||||
|
|
||||||
export default function SkillsChart() {
|
|
||||||
const data = [
|
|
||||||
{ skill: "JavaScript", score: 85, industry: 75 },
|
|
||||||
{ skill: "TypeScript", score: 80, industry: 70 },
|
|
||||||
{ skill: "React", score: 90, industry: 80 },
|
|
||||||
{ skill: "Node.js", score: 75, industry: 72 },
|
|
||||||
{ skill: "CSS", score: 88, industry: 78 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixed Chart Component
|
|
||||||
Combined bar and line chart for displaying multiple data types with different visualization methods.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MixedChart } from "@/components/ui/mixed-chart"
|
|
||||||
|
|
||||||
export default function PerformanceChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", revenue: 4000, growth: 5.2 },
|
|
||||||
{ month: "Feb", revenue: 4200, growth: 5.0 },
|
|
||||||
{ month: "Mar", revenue: 4800, growth: 14.3 },
|
|
||||||
{ month: "Apr", revenue: 5200, growth: 8.3 },
|
|
||||||
{ month: "May", revenue: 5800, growth: 11.5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Components
|
|
||||||
|
|
||||||
### Magnetic Effect Component
|
|
||||||
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Magnetic } from "@/components/ui/magnetic"
|
|
||||||
|
|
||||||
export default function InteractiveButton() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
Hover me
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animated Cursor Component
|
|
||||||
Custom animated cursor with interactive effects and particle trails for immersive experiences.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AnimatedCursor } from "@/components/ui/animated-cursor"
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Apple Hello Effect Component
|
|
||||||
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AppleHello } from "@/components/ui/apple-hello"
|
|
||||||
|
|
||||||
export default function WelcomeScreen() {
|
|
||||||
const greetings = [
|
|
||||||
{ text: "Hello", lang: "en" },
|
|
||||||
{ text: "Bonjour", lang: "fr" },
|
|
||||||
{ text: "こんにちは", lang: "ja" },
|
|
||||||
{ text: "Hola", lang: "es" },
|
|
||||||
{ text: "你好", lang: "zh" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Liquid Button Component
|
|
||||||
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LiquidButton } from "@/components/ui/liquid-button"
|
|
||||||
|
|
||||||
export default function CTASection() {
|
|
||||||
return (
|
|
||||||
console.log("CTA clicked")}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Text Component
|
|
||||||
Text animation that creates a rolling effect with smooth character transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RollingText } from "@/components/ui/rolling-text"
|
|
||||||
|
|
||||||
export default function AnimatedHeading() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shimmering Text Component
|
|
||||||
Text with animated shimmer effect for attention-grabbing headings and highlights.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ShimmeringText } from "@/components/ui/shimmering-text"
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## React Hooks
|
|
||||||
|
|
||||||
### useBoolean Hook
|
|
||||||
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useBoolean } from "@/hooks/use-boolean"
|
|
||||||
|
|
||||||
export default function TogglePanel() {
|
|
||||||
const modal = useBoolean(false)
|
|
||||||
const loading = useBoolean(false)
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
loading.setTrue()
|
|
||||||
try {
|
|
||||||
await submitForm()
|
|
||||||
modal.setFalse()
|
|
||||||
} finally {
|
|
||||||
loading.setFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Toggle Modal
|
|
||||||
{modal.value && (
|
|
||||||
|
|
||||||
|
|
||||||
Status: {loading.value ? "Saving..." : "Ready"}
|
|
||||||
|
|
||||||
Submit
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCounter Hook
|
|
||||||
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCounter } from "@/hooks/use-counter"
|
|
||||||
|
|
||||||
export default function CartCounter() {
|
|
||||||
const quantity = useCounter(0, { min: 0, max: 99 })
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
-
|
|
||||||
{quantity.value}
|
|
||||||
+
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useLocalStorage Hook
|
|
||||||
Persist state in browser localStorage with automatic serialization and deserialization.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useLocalStorage } from "@/hooks/use-local-storage"
|
|
||||||
|
|
||||||
export default function UserPreferences() {
|
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light")
|
|
||||||
const [settings, setSettings] = useLocalStorage("settings", {
|
|
||||||
notifications: true,
|
|
||||||
emailUpdates: false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(e.target.value)}>
|
|
||||||
LightDark setSettings({
|
|
||||||
...settings,
|
|
||||||
notifications: e.target.checked
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
Enable Notifications
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useDebounceValue Hook
|
|
||||||
Debounce values to prevent excessive updates and API calls during rapid user input.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useDebounceValue } from "@/hooks/use-debounce-value"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
|
|
||||||
export default function SearchBox() {
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const debouncedSearch = useDebounceValue(search, 500)
|
|
||||||
const [results, setResults] = useState([])
|
|
||||||
const [apiCalls, setApiCalls] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
setApiCalls(prev => prev + 1)
|
|
||||||
fetch(`/api/search?q=${debouncedSearch}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setResults)
|
|
||||||
}
|
|
||||||
}, [debouncedSearch])
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setSearch(e.target.value)}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
API calls: {apiCalls}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useHover Hook
|
|
||||||
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useHover } from "@/hooks/use-hover"
|
|
||||||
import { useRef } from "react"
|
|
||||||
|
|
||||||
export default function ImagePreview() {
|
|
||||||
const hoverRef = useRef(null)
|
|
||||||
const isHovering = useHover(hoverRef, {
|
|
||||||
enterDelay: 200,
|
|
||||||
leaveDelay: 100
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
{isHovering && (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCountdown Hook
|
|
||||||
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCountdown } from "@/hooks/use-countdown"
|
|
||||||
|
|
||||||
export default function OTPTimer() {
|
|
||||||
const countdown = useCountdown({
|
|
||||||
initialSeconds: 60,
|
|
||||||
onComplete: () => alert("OTP expired! Request a new code.")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
{countdown.seconds}s
|
|
||||||
|
|
||||||
{!countdown.isRunning ? (
|
|
||||||
Start
|
|
||||||
) : (
|
|
||||||
Pause
|
|
||||||
)}
|
|
||||||
Reset
|
|
||||||
|
|
||||||
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation and Usage
|
|
||||||
|
|
||||||
### CLI Installation
|
|
||||||
Install components directly into your project using the shadcn CLI for instant integration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize shadcn in your project
|
|
||||||
npx shadcn@latest init
|
|
||||||
|
|
||||||
# Add individual components
|
|
||||||
npx shadcn@latest add terminal
|
|
||||||
npx shadcn@latest add dock
|
|
||||||
npx shadcn@latest add credit-card
|
|
||||||
|
|
||||||
# Add multiple components at once
|
|
||||||
npx shadcn@latest add bar-chart line-chart pie-chart
|
|
||||||
|
|
||||||
# Add hooks
|
|
||||||
npx shadcn@latest add use-boolean use-counter use-local-storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Configuration
|
|
||||||
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tailwind.config.ts
|
|
||||||
import type { Config } from "tailwindcss"
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./pages/**/*.{ts,tsx}",
|
|
||||||
"./components/**/*.{ts,tsx}",
|
|
||||||
"./app/**/*.{ts,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
|
|
||||||
|
|
||||||
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.
|
|
||||||
File diff suppressed because it is too large
Load diff
1
.augment/skills/shadcn-ui
Symbolic link
1
.augment/skills/shadcn-ui
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,306 +0,0 @@
|
||||||
### shadcn/ui Chart Component - Installation
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add chart
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Basic Usage
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", desktop: 186, mobile: 80 },
|
|
||||||
{ month: "February", desktop: 305, mobile: 200 },
|
|
||||||
{ month: "March", desktop: 237, mobile: 120 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function BarChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
||||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - ChartConfig with Custom Colors
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
You can define custom colors directly in the configuration using hex values or CSS variables.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "#2563eb",
|
|
||||||
theme: {
|
|
||||||
light: "#2563eb",
|
|
||||||
dark: "#60a5fa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - CSS Variables
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Add chart color variables to your globals.css for consistent theming.
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Chart colors */
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.546 0.198 38.228);
|
|
||||||
--chart-4: oklch(0.596 0.151 343.253);
|
|
||||||
--chart-5: oklch(0.546 0.158 49.157);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.698 0.141 24.311);
|
|
||||||
--chart-4: oklch(0.676 0.172 171.196);
|
|
||||||
--chart-5: oklch(0.578 0.192 302.85);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Line Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a line chart with shadcn/ui charts component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
price: {
|
|
||||||
label: "Price",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", price: 186 },
|
|
||||||
{ month: "February", price: 305 },
|
|
||||||
{ month: "March", price: 237 },
|
|
||||||
{ month: "April", price: 203 },
|
|
||||||
{ month: "May", price: 276 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function LineChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
|
|
||||||
<Line
|
|
||||||
dataKey="price"
|
|
||||||
stroke="var(--color-price)"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Area Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating an area chart with gradient fill and legend.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Area, AreaChart, XAxis, YAxis } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
||||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
export function AreaChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<AreaChart data={chartData}>
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} />
|
|
||||||
<Area
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
stroke="var(--color-desktop)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
stroke="var(--color-mobile)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Pie Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a pie/donut chart with shadcn/ui.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Pie, PieChart } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
||||||
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
||||||
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const pieData = [
|
|
||||||
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
|
|
||||||
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
|
|
||||||
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PieChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<PieChart>
|
|
||||||
<Pie
|
|
||||||
data={pieData}
|
|
||||||
dataKey="visitors"
|
|
||||||
nameKey="browser"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
outerRadius={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</PieChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui ChartTooltipContent Props
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
|
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
|
||||||
|------|------|---------|-------------|
|
|
||||||
| `labelKey` | string | "label" | Key for tooltip label |
|
|
||||||
| `nameKey` | string | "name" | Key for tooltip name |
|
|
||||||
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
|
|
||||||
| `hideLabel` | boolean | false | Hide label |
|
|
||||||
| `hideIndicator` | boolean | false | Hide indicator |
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Accessibility
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<BarChart accessibilityLayer data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
- Keyboard arrow key navigation
|
|
||||||
- ARIA labels for chart elements
|
|
||||||
- Screen reader announcements for data values
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Recharts Dependencies
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component requires the following Recharts dependencies to be installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add recharts
|
|
||||||
npm install recharts
|
|
||||||
yarn add recharts
|
|
||||||
```
|
|
||||||
|
|
||||||
Recharts provides the following chart types:
|
|
||||||
- Area, Bar, Line, Pie, Composed
|
|
||||||
- Radar, RadialBar, Scatter
|
|
||||||
- Funnel, Treemap
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# shadcn/ui Learning Guide
|
|
||||||
|
|
||||||
This guide helps you learn shadcn/ui from basics to advanced patterns.
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
### 1. Understanding the Philosophy
|
|
||||||
|
|
||||||
shadcn/ui is different from traditional component libraries:
|
|
||||||
|
|
||||||
- **Copy-paste components**: Components are copied into your project, not installed as packages
|
|
||||||
- **Full customization**: You own the code and can modify it freely
|
|
||||||
- **Built on Radix UI**: Provides accessibility primitives
|
|
||||||
- **Styled with Tailwind**: Uses utility classes for consistent styling
|
|
||||||
|
|
||||||
### 2. Core Concepts to Master
|
|
||||||
|
|
||||||
#### Class Variance Authority (CVA)
|
|
||||||
Most components use CVA for variant management:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"base-classes",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "variant-classes",
|
|
||||||
destructive: "destructive-classes",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "size-classes",
|
|
||||||
sm: "small-classes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cn Utility Function
|
|
||||||
The `cn` function combines classes and resolves conflicts:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation Checklist
|
|
||||||
|
|
||||||
- [ ] Initialize a new project (Next.js, Vite, or Remix)
|
|
||||||
- [ ] Install Tailwind CSS
|
|
||||||
- [ ] Run `npx shadcn@latest init`
|
|
||||||
- [ ] Configure CSS variables
|
|
||||||
- [ ] Install first component: `npx shadcn@latest add button`
|
|
||||||
|
|
||||||
### 4. Essential Components to Learn First
|
|
||||||
|
|
||||||
1. **Button** - Learn variants and sizes
|
|
||||||
2. **Input** - Form inputs with labels
|
|
||||||
3. **Card** - Container components
|
|
||||||
4. **Form** - Form handling with React Hook Form
|
|
||||||
5. **Dialog** - Modal windows
|
|
||||||
6. **Select** - Dropdown selections
|
|
||||||
7. **Toast** - Notifications
|
|
||||||
|
|
||||||
### 5. Common Patterns
|
|
||||||
|
|
||||||
#### Form Pattern
|
|
||||||
Every form follows this structure:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
1. Define Zod schema
|
|
||||||
2. Create form with useForm
|
|
||||||
3. Wrap with Form component
|
|
||||||
4. Add FormField for each input
|
|
||||||
5. Handle submission
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Component Customization Pattern
|
|
||||||
To customize a component:
|
|
||||||
|
|
||||||
1. Copy component to your project
|
|
||||||
2. Modify the variants
|
|
||||||
3. Add new props if needed
|
|
||||||
4. Update types
|
|
||||||
|
|
||||||
### 6. Best Practices
|
|
||||||
|
|
||||||
- Always use TypeScript
|
|
||||||
- Follow the existing component structure
|
|
||||||
- Use semantic HTML when possible
|
|
||||||
- Test with screen readers for accessibility
|
|
||||||
- Keep components small and focused
|
|
||||||
|
|
||||||
### 7. Advanced Topics
|
|
||||||
|
|
||||||
- Creating custom components from scratch
|
|
||||||
- Building complex forms with validation
|
|
||||||
- Implementing dark mode
|
|
||||||
- Optimizing for performance
|
|
||||||
- Testing components
|
|
||||||
|
|
||||||
## Practice Exercises
|
|
||||||
|
|
||||||
### Exercise 1: Basic Setup
|
|
||||||
1. Create a new Next.js project
|
|
||||||
2. Set up shadcn/ui
|
|
||||||
3. Install and customize a Button component
|
|
||||||
4. Add a new variant "gradient"
|
|
||||||
|
|
||||||
### Exercise 2: Form Building
|
|
||||||
1. Create a contact form with:
|
|
||||||
- Name input (required)
|
|
||||||
- Email input (email validation)
|
|
||||||
- Message textarea (min length)
|
|
||||||
- Submit button with loading state
|
|
||||||
|
|
||||||
### Exercise 3: Component Combination
|
|
||||||
1. Build a settings page using:
|
|
||||||
- Card for layout
|
|
||||||
- Sheet for mobile menu
|
|
||||||
- Select for dropdowns
|
|
||||||
- Switch for toggles
|
|
||||||
- Toast for notifications
|
|
||||||
|
|
||||||
### Exercise 4: Custom Component
|
|
||||||
1. Create a custom Badge component
|
|
||||||
2. Support variants: default, secondary, destructive, outline
|
|
||||||
3. Support sizes: sm, default, lg
|
|
||||||
4. Add icon support
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Official Documentation](https://ui.shadcn.com)
|
|
||||||
- [GitHub Repository](https://github.com/shadcn/ui)
|
|
||||||
- [Examples Gallery](https://ui.shadcn.com/examples)
|
|
||||||
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
|
|
||||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,586 +0,0 @@
|
||||||
# shadcn.io Component Library
|
|
||||||
|
|
||||||
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
|
|
||||||
|
|
||||||
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
|
|
||||||
|
|
||||||
## Core Components
|
|
||||||
|
|
||||||
### Terminal Component
|
|
||||||
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Terminal } from "@/components/ui/terminal"
|
|
||||||
|
|
||||||
export default function DemoTerminal() {
|
|
||||||
return (
|
|
||||||
npm install @repo/terminalInstalling dependencies...npm start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dock Component
|
|
||||||
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Dock, DockIcon } from "@/components/ui/dock"
|
|
||||||
import { Home, Settings, User, Mail } from "lucide-react"
|
|
||||||
|
|
||||||
export default function AppDock() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credit Card Component
|
|
||||||
Interactive 3D credit card component with flip animations for payment forms and card displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { CreditCard } from "@/components/ui/credit-card"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function PaymentForm() {
|
|
||||||
const [cardData, setCardData] = useState({
|
|
||||||
number: "4532 1234 5678 9010",
|
|
||||||
holder: "JOHN DOE",
|
|
||||||
expiry: "12/28",
|
|
||||||
cvv: "123"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
console.log("Card flipped:", flipped)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Zoom Component
|
|
||||||
Zoomable image component with smooth modal transitions for image galleries and product displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ImageZoom } from "@/components/ui/image-zoom"
|
|
||||||
|
|
||||||
export default function ProductGallery() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Code Component
|
|
||||||
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { QRCode } from "@/components/ui/qr-code"
|
|
||||||
|
|
||||||
export default function ShareDialog() {
|
|
||||||
const shareUrl = "https://shadcn.io"
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Scan to visit shadcn.io
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Picker Component
|
|
||||||
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ColorPicker } from "@/components/ui/color-picker"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function ThemeCustomizer() {
|
|
||||||
const [color, setColor] = useState("#3b82f6")
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Selected: {color}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chart Components
|
|
||||||
|
|
||||||
### Bar Chart Component
|
|
||||||
Clean bar chart component for data comparison and categorical analysis using Recharts.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { BarChart } from "@/components/ui/bar-chart"
|
|
||||||
|
|
||||||
export default function SalesChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", sales: 4000, revenue: 2400 },
|
|
||||||
{ month: "Feb", sales: 3000, revenue: 1398 },
|
|
||||||
{ month: "Mar", sales: 2000, revenue: 9800 },
|
|
||||||
{ month: "Apr", sales: 2780, revenue: 3908 },
|
|
||||||
{ month: "May", sales: 1890, revenue: 4800 },
|
|
||||||
{ month: "Jun", sales: 2390, revenue: 3800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`$${value.toLocaleString()}`}
|
|
||||||
yAxisWidth={60}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Chart Component
|
|
||||||
Smooth line chart for visualizing trends and time-series data with multiple data series support.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LineChart } from "@/components/ui/line-chart"
|
|
||||||
|
|
||||||
export default function MetricsChart() {
|
|
||||||
const data = [
|
|
||||||
{ date: "2024-01", users: 1200, sessions: 3400 },
|
|
||||||
{ date: "2024-02", users: 1800, sessions: 4200 },
|
|
||||||
{ date: "2024-03", users: 2400, sessions: 5800 },
|
|
||||||
{ date: "2024-04", users: 3100, sessions: 7200 },
|
|
||||||
{ date: "2024-05", users: 3800, sessions: 8900 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Component
|
|
||||||
Donut chart component for displaying proportional data and percentage distributions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { PieChart } from "@/components/ui/pie-chart"
|
|
||||||
|
|
||||||
export default function MarketShareChart() {
|
|
||||||
const data = [
|
|
||||||
{ name: "Product A", value: 400, fill: "#3b82f6" },
|
|
||||||
{ name: "Product B", value: 300, fill: "#10b981" },
|
|
||||||
{ name: "Product C", value: 300, fill: "#f59e0b" },
|
|
||||||
{ name: "Product D", value: 200, fill: "#ef4444" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${entry.name}: ${entry.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Area Chart Component
|
|
||||||
Stacked area chart for visualizing volume changes over time with multiple data series.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AreaChart } from "@/components/ui/area-chart"
|
|
||||||
|
|
||||||
export default function TrafficChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
|
|
||||||
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
|
|
||||||
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
|
|
||||||
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
|
|
||||||
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Radar Chart Component
|
|
||||||
Multi-axis chart for comparing multiple variables across different categories simultaneously.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RadarChart } from "@/components/ui/radar-chart"
|
|
||||||
|
|
||||||
export default function SkillsChart() {
|
|
||||||
const data = [
|
|
||||||
{ skill: "JavaScript", score: 85, industry: 75 },
|
|
||||||
{ skill: "TypeScript", score: 80, industry: 70 },
|
|
||||||
{ skill: "React", score: 90, industry: 80 },
|
|
||||||
{ skill: "Node.js", score: 75, industry: 72 },
|
|
||||||
{ skill: "CSS", score: 88, industry: 78 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixed Chart Component
|
|
||||||
Combined bar and line chart for displaying multiple data types with different visualization methods.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MixedChart } from "@/components/ui/mixed-chart"
|
|
||||||
|
|
||||||
export default function PerformanceChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", revenue: 4000, growth: 5.2 },
|
|
||||||
{ month: "Feb", revenue: 4200, growth: 5.0 },
|
|
||||||
{ month: "Mar", revenue: 4800, growth: 14.3 },
|
|
||||||
{ month: "Apr", revenue: 5200, growth: 8.3 },
|
|
||||||
{ month: "May", revenue: 5800, growth: 11.5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Components
|
|
||||||
|
|
||||||
### Magnetic Effect Component
|
|
||||||
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Magnetic } from "@/components/ui/magnetic"
|
|
||||||
|
|
||||||
export default function InteractiveButton() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
Hover me
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animated Cursor Component
|
|
||||||
Custom animated cursor with interactive effects and particle trails for immersive experiences.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AnimatedCursor } from "@/components/ui/animated-cursor"
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Apple Hello Effect Component
|
|
||||||
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AppleHello } from "@/components/ui/apple-hello"
|
|
||||||
|
|
||||||
export default function WelcomeScreen() {
|
|
||||||
const greetings = [
|
|
||||||
{ text: "Hello", lang: "en" },
|
|
||||||
{ text: "Bonjour", lang: "fr" },
|
|
||||||
{ text: "こんにちは", lang: "ja" },
|
|
||||||
{ text: "Hola", lang: "es" },
|
|
||||||
{ text: "你好", lang: "zh" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Liquid Button Component
|
|
||||||
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LiquidButton } from "@/components/ui/liquid-button"
|
|
||||||
|
|
||||||
export default function CTASection() {
|
|
||||||
return (
|
|
||||||
console.log("CTA clicked")}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Text Component
|
|
||||||
Text animation that creates a rolling effect with smooth character transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RollingText } from "@/components/ui/rolling-text"
|
|
||||||
|
|
||||||
export default function AnimatedHeading() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shimmering Text Component
|
|
||||||
Text with animated shimmer effect for attention-grabbing headings and highlights.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ShimmeringText } from "@/components/ui/shimmering-text"
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## React Hooks
|
|
||||||
|
|
||||||
### useBoolean Hook
|
|
||||||
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useBoolean } from "@/hooks/use-boolean"
|
|
||||||
|
|
||||||
export default function TogglePanel() {
|
|
||||||
const modal = useBoolean(false)
|
|
||||||
const loading = useBoolean(false)
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
loading.setTrue()
|
|
||||||
try {
|
|
||||||
await submitForm()
|
|
||||||
modal.setFalse()
|
|
||||||
} finally {
|
|
||||||
loading.setFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Toggle Modal
|
|
||||||
{modal.value && (
|
|
||||||
|
|
||||||
|
|
||||||
Status: {loading.value ? "Saving..." : "Ready"}
|
|
||||||
|
|
||||||
Submit
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCounter Hook
|
|
||||||
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCounter } from "@/hooks/use-counter"
|
|
||||||
|
|
||||||
export default function CartCounter() {
|
|
||||||
const quantity = useCounter(0, { min: 0, max: 99 })
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
-
|
|
||||||
{quantity.value}
|
|
||||||
+
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useLocalStorage Hook
|
|
||||||
Persist state in browser localStorage with automatic serialization and deserialization.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useLocalStorage } from "@/hooks/use-local-storage"
|
|
||||||
|
|
||||||
export default function UserPreferences() {
|
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light")
|
|
||||||
const [settings, setSettings] = useLocalStorage("settings", {
|
|
||||||
notifications: true,
|
|
||||||
emailUpdates: false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(e.target.value)}>
|
|
||||||
LightDark setSettings({
|
|
||||||
...settings,
|
|
||||||
notifications: e.target.checked
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
Enable Notifications
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useDebounceValue Hook
|
|
||||||
Debounce values to prevent excessive updates and API calls during rapid user input.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useDebounceValue } from "@/hooks/use-debounce-value"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
|
|
||||||
export default function SearchBox() {
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const debouncedSearch = useDebounceValue(search, 500)
|
|
||||||
const [results, setResults] = useState([])
|
|
||||||
const [apiCalls, setApiCalls] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
setApiCalls(prev => prev + 1)
|
|
||||||
fetch(`/api/search?q=${debouncedSearch}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setResults)
|
|
||||||
}
|
|
||||||
}, [debouncedSearch])
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setSearch(e.target.value)}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
API calls: {apiCalls}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useHover Hook
|
|
||||||
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useHover } from "@/hooks/use-hover"
|
|
||||||
import { useRef } from "react"
|
|
||||||
|
|
||||||
export default function ImagePreview() {
|
|
||||||
const hoverRef = useRef(null)
|
|
||||||
const isHovering = useHover(hoverRef, {
|
|
||||||
enterDelay: 200,
|
|
||||||
leaveDelay: 100
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
{isHovering && (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCountdown Hook
|
|
||||||
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCountdown } from "@/hooks/use-countdown"
|
|
||||||
|
|
||||||
export default function OTPTimer() {
|
|
||||||
const countdown = useCountdown({
|
|
||||||
initialSeconds: 60,
|
|
||||||
onComplete: () => alert("OTP expired! Request a new code.")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
{countdown.seconds}s
|
|
||||||
|
|
||||||
{!countdown.isRunning ? (
|
|
||||||
Start
|
|
||||||
) : (
|
|
||||||
Pause
|
|
||||||
)}
|
|
||||||
Reset
|
|
||||||
|
|
||||||
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation and Usage
|
|
||||||
|
|
||||||
### CLI Installation
|
|
||||||
Install components directly into your project using the shadcn CLI for instant integration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize shadcn in your project
|
|
||||||
npx shadcn@latest init
|
|
||||||
|
|
||||||
# Add individual components
|
|
||||||
npx shadcn@latest add terminal
|
|
||||||
npx shadcn@latest add dock
|
|
||||||
npx shadcn@latest add credit-card
|
|
||||||
|
|
||||||
# Add multiple components at once
|
|
||||||
npx shadcn@latest add bar-chart line-chart pie-chart
|
|
||||||
|
|
||||||
# Add hooks
|
|
||||||
npx shadcn@latest add use-boolean use-counter use-local-storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Configuration
|
|
||||||
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tailwind.config.ts
|
|
||||||
import type { Config } from "tailwindcss"
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./pages/**/*.{ts,tsx}",
|
|
||||||
"./components/**/*.{ts,tsx}",
|
|
||||||
"./app/**/*.{ts,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
|
|
||||||
|
|
||||||
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.
|
|
||||||
File diff suppressed because it is too large
Load diff
3
.beads/agent.txt
Normal file
3
.beads/agent.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
✓ Created issue: beadboard-8bs — Agent: Antigravity Graph Orchestrator
|
||||||
|
Priority: P0
|
||||||
|
Status: open
|
||||||
61
.beads/create_help.txt
Normal file
61
.beads/create_help.txt
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
Create a new issue (or multiple issues from markdown file)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
bd create [title] [flags]
|
||||||
|
|
||||||
|
Aliases:
|
||||||
|
create, new
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--acceptance string Acceptance criteria
|
||||||
|
--agent-rig string Agent's rig name (requires --type=agent)
|
||||||
|
--append-notes string Append to existing notes (with newline separator)
|
||||||
|
-a, --assignee string Assignee
|
||||||
|
--body-file string Read description from file (use - for stdin)
|
||||||
|
--defer string Defer until date (issue hidden from bd ready until then). Same formats as --due
|
||||||
|
--deps strings Dependencies in format 'type:id' or 'id' (e.g., 'discovered-from:bd-20,blocks:bd-15' or 'bd-20')
|
||||||
|
-d, --description string Issue description
|
||||||
|
--design string Design notes
|
||||||
|
--dry-run Preview what would be created without actually creating
|
||||||
|
--due string Due date/time. Formats: +6h, +1d, +2w, tomorrow, next monday, 2025-01-15
|
||||||
|
--ephemeral Create as ephemeral (short-lived, subject to TTL compaction)
|
||||||
|
-e, --estimate int Time estimate in minutes (e.g., 60 for 1 hour)
|
||||||
|
--event-actor string Entity URI who caused this event (requires --type=event)
|
||||||
|
--event-category string Event category (e.g., patrol.muted, agent.started) (requires --type=event)
|
||||||
|
--event-payload string Event-specific JSON data (requires --type=event)
|
||||||
|
--event-target string Entity URI or bead ID affected (requires --type=event)
|
||||||
|
--external-ref string External reference (e.g., 'gh-9', 'jira-ABC')
|
||||||
|
-f, --file string Create multiple issues from markdown file
|
||||||
|
--force Force creation even if prefix doesn't match database prefix
|
||||||
|
-h, --help help for create
|
||||||
|
--id string Explicit issue ID (e.g., 'bd-42' for partitioning)
|
||||||
|
-l, --labels strings Labels (comma-separated)
|
||||||
|
--metadata string Set custom metadata (JSON string or @file.json to read from file)
|
||||||
|
--mol-type string Molecule type: swarm (multi-polecat), patrol (recurring ops), work (default)
|
||||||
|
--no-inherit-labels Don't inherit labels from parent issue
|
||||||
|
--notes string Additional notes
|
||||||
|
--parent string Parent issue ID for hierarchical child (e.g., 'bd-a3f8e9')
|
||||||
|
--prefix string Create issue in rig by prefix (e.g., --prefix bd- or --prefix bd or --prefix beads)
|
||||||
|
-p, --priority string Priority (0-4 or P0-P4, 0=highest) (default "2")
|
||||||
|
--repo string Target repository for issue (overrides auto-routing)
|
||||||
|
--rig string Create issue in a different rig (e.g., --rig beads)
|
||||||
|
--silent Output only the issue ID (for scripting)
|
||||||
|
--spec-id string Link to specification document
|
||||||
|
--title string Issue title (alternative to positional argument)
|
||||||
|
-t, --type string Issue type (bug|feature|task|epic|chore|decision); custom types require types.custom config; aliases: enhancement/feat→feature, dec/adr→decision (default "task")
|
||||||
|
--validate Validate description contains required sections for issue type
|
||||||
|
--waits-for string Spawner issue ID to wait for (creates waits-for dependency for fanout gate)
|
||||||
|
--waits-for-gate string Gate type: all-children (wait for all) or any-children (wait for first) (default "all-children")
|
||||||
|
--wisp-type string Wisp type for TTL-based compaction: heartbeat, ping, patrol, gc_report, recovery, error, escalation
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--actor string Actor name for audit trail (default: $BD_ACTOR, git user.name, $USER)
|
||||||
|
--allow-stale Allow operations on potentially stale data (skip staleness check)
|
||||||
|
--db string Database path (default: auto-discover .beads/*.db)
|
||||||
|
--dolt-auto-commit string Dolt auto-commit policy (off|on|batch). 'on': commit after each write. 'batch': defer commits to bd sync / bd dolt commit; uncommitted changes persist in the working set until then. SIGTERM/SIGHUP flush pending batch commits. Default: off. Override via config key dolt.auto-commit
|
||||||
|
--json Output in JSON format
|
||||||
|
--profile Generate CPU profile for performance analysis
|
||||||
|
-q, --quiet Suppress non-essential output (errors only)
|
||||||
|
--readonly Read-only mode: block write operations (for worker sandboxes)
|
||||||
|
--sandbox Sandbox mode: disables auto-sync
|
||||||
|
-v, --verbose Enable verbose/debug output
|
||||||
1
.beads/debug.txt
Normal file
1
.beads/debug.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
✓ Updated issue: beadboard-txj.2 — Apply Status Colors and Transitive Context to Graph Edges
|
||||||
42
.beads/dep_help.txt
Normal file
42
.beads/dep_help.txt
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
Manage dependencies between issues.
|
||||||
|
|
||||||
|
When called with an issue ID and --blocks flag, creates a blocking dependency:
|
||||||
|
bd dep <blocker-id> --blocks <blocked-id>
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
bd dep add <blocked-id> <blocker-id>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bd dep bd-xyz --blocks bd-abc # bd-xyz blocks bd-abc
|
||||||
|
bd dep add bd-abc bd-xyz # Same as above (bd-abc depends on bd-xyz)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
bd dep [issue-id] [flags]
|
||||||
|
bd dep [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
add Add a dependency
|
||||||
|
cycles Detect dependency cycles
|
||||||
|
list List dependencies or dependents of an issue
|
||||||
|
relate Create a bidirectional relates_to link between issues
|
||||||
|
remove Remove a dependency
|
||||||
|
tree Show dependency tree
|
||||||
|
unrelate Remove a relates_to link between issues
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-b, --blocks string Issue ID that this issue blocks (shorthand for: bd dep add <blocked> <blocker>)
|
||||||
|
-h, --help help for dep
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--actor string Actor name for audit trail (default: $BD_ACTOR, git user.name, $USER)
|
||||||
|
--allow-stale Allow operations on potentially stale data (skip staleness check)
|
||||||
|
--db string Database path (default: auto-discover .beads/*.db)
|
||||||
|
--dolt-auto-commit string Dolt auto-commit policy (off|on|batch). 'on': commit after each write. 'batch': defer commits to bd sync / bd dolt commit; uncommitted changes persist in the working set until then. SIGTERM/SIGHUP flush pending batch commits. Default: off. Override via config key dolt.auto-commit
|
||||||
|
--json Output in JSON format
|
||||||
|
--profile Generate CPU profile for performance analysis
|
||||||
|
-q, --quiet Suppress non-essential output (errors only)
|
||||||
|
--readonly Read-only mode: block write operations (for worker sandboxes)
|
||||||
|
--sandbox Sandbox mode: disables auto-sync
|
||||||
|
-v, --verbose Enable verbose/debug output
|
||||||
|
|
||||||
|
Use "bd dep [command] --help" for more information about a command.
|
||||||
|
|
@ -1 +1 @@
|
||||||
3840
|
1910
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
1772407425
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1 +0,0 @@
|
||||||
60816
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
3307
|
|
||||||
3
.beads/epic_create.txt
Normal file
3
.beads/epic_create.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
✓ Created issue: beadboard-txj — Epic: Enhanced Graph Edge Visualization
|
||||||
|
Priority: P0
|
||||||
|
Status: open
|
||||||
60
.beads/lint_output.txt
Normal file
60
.beads/lint_output.txt
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
|
||||||
|
> beadboard@0.1.0 lint
|
||||||
|
> eslint .
|
||||||
|
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\.beads\fix.js
|
||||||
|
1:12 error A `require()` style import is forbidden @typescript-eslint/no-require-imports
|
||||||
|
2:14 error A `require()` style import is forbidden @typescript-eslint/no-require-imports
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\.beads\fix2.js
|
||||||
|
1:12 error A `require()` style import is forbidden @typescript-eslint/no-require-imports
|
||||||
|
2:14 error A `require()` style import is forbidden @typescript-eslint/no-require-imports
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\activity\activity-panel.tsx
|
||||||
|
285:16 warning 'e' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\activity\swarm-command-feed.tsx
|
||||||
|
83:22 warning 'e' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\graph\dependency-graph-page.tsx
|
||||||
|
641:17 warning 'unused_' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\graph\smart-dag.tsx
|
||||||
|
38:15 warning 'hideClosedProp' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
56:45 warning '_id' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
62:44 warning '_id' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
84:5 warning 'signalById' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
85:5 warning 'cycleNodeIdSet' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
87:5 warning 'blockerTooltipMap' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
170:53 warning 'shouldOpenDrawer' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
176:9 warning 'selectedIssue' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\shared\top-bar.tsx
|
||||||
|
93:82 warning 'toggleBlockedOnly' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\shared\unified-shell.tsx
|
||||||
|
42:55 warning 'panel' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\shared\workflow-graph.tsx
|
||||||
|
251:17 warning 'unused_' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\social\social-card.tsx
|
||||||
|
116:3 warning 'onOpenThread' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\swarm\swarm-detail.tsx
|
||||||
|
4:15 warning 'SwarmCardData' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
6:10 warning 'cn' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
53:16 warning 'e' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\components\swarm\swarm-page.tsx
|
||||||
|
18:53 warning 'LayoutGrid' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\hooks\use-mission-graph.ts
|
||||||
|
33:16 warning '_e' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
C:\Users\Zenchant\codex\beadboard\src\hooks\use-swarm-topology.ts
|
||||||
|
35:16 warning '_err' is defined but never used @typescript-eslint/no-unused-vars
|
||||||
|
|
||||||
|
Γ£û 25 problems (4 errors, 21 warnings)
|
||||||
|
|
||||||
123
.beads/mem.txt
Normal file
123
.beads/mem.txt
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
✓ beadboard-116 · [MEMORY][WORKFLOW][HARD] Evidence before completion claims [● P1 · CLOSED]
|
||||||
|
Owner: ZenchantLive · Type: decision
|
||||||
|
Created: 2026-03-02 · Updated: 2026-03-02
|
||||||
|
Close reason: Ratified canonical memory
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Scope: All new implementation tasks that change behavior or data flow.
|
||||||
|
Out of Scope: Purely informational discussions without code or workflow changes.
|
||||||
|
Rule: Start from explicit verification evidence and update work state in bd before claiming progress complete.
|
||||||
|
Rationale: Prevents status drift and false completion claims.
|
||||||
|
Failure Mode: Unverified completion claims lead to regressions and mistrust.
|
||||||
|
|
||||||
|
|
||||||
|
NOTES
|
||||||
|
Provenance linked: bb-92d.6, beadboard-jgy, beadboard-yz6
|
||||||
|
|
||||||
|
ACCEPTANCE CRITERIA
|
||||||
|
Given an implementation task, when work begins and ends, then bd state transitions and verification command evidence are recorded.
|
||||||
|
Verification command(s): bd show <task-id>; npm run typecheck; npm run lint; npm run test
|
||||||
|
|
||||||
|
LABELS: mem-canonical, mem-hard, memory, memory-workflow
|
||||||
|
|
||||||
|
METADATA
|
||||||
|
domain: memory-workflow
|
||||||
|
effective_date: 2026-03-02
|
||||||
|
evidence_ids: bb-92d.6,beadboard-jgy,beadboard-yz6
|
||||||
|
memory_strength: hard
|
||||||
|
memory_version: 1
|
||||||
|
owner: team
|
||||||
|
plan_refs: docs/plans/2026-03-01-beads-native-memory.md,docs/plans/2026-03-02-bd-memory-fabric-design.md
|
||||||
|
superseded_by: null
|
||||||
|
supersedes: null
|
||||||
|
|
||||||
|
RELATED
|
||||||
|
↔ ✓ bb-92d.6: Add guardrail test preventing direct writes to .beads/issues.jsonl ● P0
|
||||||
|
↔ ✓ beadboard-jgy: Document memory workflow in help/memory and AGENTS manuals ● P1
|
||||||
|
↔ ○ beadboard-nq9: (EPIC) [MEMORY-ANCHOR] Workflow Protocol ● P1
|
||||||
|
↔ ✓ beadboard-yz6: Bootstrap Phase 1 Memory Fabric (anchors + canonical nodes) ● P1
|
||||||
|
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
✓ beadboard-60a · [MEMORY][ARCH][HARD] Dependencies model execution order, not visual order [● P1 · CLOSED]
|
||||||
|
Owner: ZenchantLive · Type: decision
|
||||||
|
Created: 2026-03-02 · Updated: 2026-03-02
|
||||||
|
Close reason: Ratified canonical memory
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Scope: Dependency graph design and updates.
|
||||||
|
Out of Scope: Visual layout decisions that do not alter execution semantics.
|
||||||
|
Rule: Dependencies encode execution order and blocking semantics, never visual grouping.
|
||||||
|
Rationale: Keeps ready/blocked states truthful and machine-reliable.
|
||||||
|
Failure Mode: Incorrect dependency direction causes false blocking or unsafe parallelism.
|
||||||
|
|
||||||
|
|
||||||
|
NOTES
|
||||||
|
Provenance linked: bb-bvn, beadboard-r1i, beadboard-68k
|
||||||
|
|
||||||
|
ACCEPTANCE CRITERIA
|
||||||
|
Given a dependency update, when graph state is queried, then blocked/ready outcomes match intended execution order.
|
||||||
|
Verification command(s): bd dep tree <issue-id>; bd blocked; bd ready
|
||||||
|
|
||||||
|
LABELS: mem-canonical, mem-hard, memory, memory-arch
|
||||||
|
|
||||||
|
METADATA
|
||||||
|
domain: memory-arch
|
||||||
|
effective_date: 2026-03-02
|
||||||
|
evidence_ids: bb-bvn,beadboard-r1i,beadboard-68k
|
||||||
|
memory_strength: hard
|
||||||
|
memory_version: 1
|
||||||
|
owner: team
|
||||||
|
plan_refs: docs/plans/2026-02-22-dag-views-ux-design.md
|
||||||
|
superseded_by: null
|
||||||
|
supersedes: null
|
||||||
|
|
||||||
|
RELATED
|
||||||
|
↔ ✓ bb-bvn: Dependency Graph (React Flow) ● P0
|
||||||
|
↔ ✓ beadboard-68k: Phase 0: UX Wiring Fixes ● P0
|
||||||
|
↔ ○ beadboard-76p: (EPIC) [MEMORY-ANCHOR] Architecture ● P1
|
||||||
|
↔ ✓ beadboard-r1i: Phase 1: Contextual Right Panel ● P0
|
||||||
|
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
✓ beadboard-zas · [MEMORY][ARCH][HARD] Shared logic for cross-view behavior [● P1 · CLOSED]
|
||||||
|
Owner: ZenchantLive · Type: decision
|
||||||
|
Created: 2026-03-02 · Updated: 2026-03-02
|
||||||
|
Close reason: Ratified canonical memory
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Scope: Repeated logic used by multiple views or pages.
|
||||||
|
Out of Scope: One-off prototypes and disposable experiments.
|
||||||
|
Rule: Reuse shared paths/components for cross-view behavior; avoid one-off logic drift.
|
||||||
|
Rationale: Prevents silent divergence between Kanban, Graph, and API views.
|
||||||
|
Failure Mode: Patching one surface only creates inconsistent user behavior.
|
||||||
|
|
||||||
|
|
||||||
|
NOTES
|
||||||
|
Provenance linked: beadboard-68k.4, beadboard-68k.5, beadboard-r1i.1
|
||||||
|
|
||||||
|
ACCEPTANCE CRITERIA
|
||||||
|
Given a behavior change touching multiple views, when implementation lands, then shared logic path is reused and covered by tests.
|
||||||
|
Verification command(s): rg "<shared-function-or-hook>" src tests; npm run test
|
||||||
|
|
||||||
|
LABELS: mem-canonical, mem-hard, memory, memory-arch
|
||||||
|
|
||||||
|
METADATA
|
||||||
|
domain: memory-arch
|
||||||
|
effective_date: 2026-03-02
|
||||||
|
evidence_ids: beadboard-68k.4,beadboard-68k.5,beadboard-r1i.1
|
||||||
|
memory_strength: hard
|
||||||
|
memory_version: 1
|
||||||
|
owner: team
|
||||||
|
plan_refs: docs/plans/2026-02-22-dag-views-ux-design.md,docs/plans/2026-02-15-unified-ux-prd.md
|
||||||
|
superseded_by: null
|
||||||
|
supersedes: null
|
||||||
|
|
||||||
|
RELATED
|
||||||
|
↔ ✓ beadboard-68k.4: Fix thread drawer status badge (hardcoded 'In Progress') ● P0
|
||||||
|
↔ ✓ beadboard-68k.5: Wire TopBar metric tiles from live issue data ● P0
|
||||||
|
↔ ○ beadboard-76p: (EPIC) [MEMORY-ANCHOR] Architecture ● P1
|
||||||
|
↔ ✓ beadboard-r1i.1: Extend ContextualRightPanel props and thread taskId/swarmId from shell ● P0
|
||||||
|
|
||||||
794
.beads/npm_test_output.txt
Normal file
794
.beads/npm_test_output.txt
Normal file
|
|
@ -0,0 +1,794 @@
|
||||||
|
|
||||||
|
> beadboard@0.1.0 test
|
||||||
|
> node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/blocked-triage-modal.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/components/graph/graph-node-conversation.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/install-manifest.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx && node --import tsx --test tests/scripts/beadboard-launcher.test.ts && node --import tsx --test tests/scripts/install-wrappers-contract.test.ts && node --import tsx --test tests/scripts/install-sh-smoke.test.ts && node --import tsx --test tests/scripts/installer-ci-contract.test.ts && node --import tsx --test tests/docs/installer-quickstart-contract.test.ts && node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts && node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts && node --import tsx --test tests/skills/beadboard-driver/generate-agent-name.test.ts && node --import tsx --test tests/skills/beadboard-driver/readiness-report.test.ts && node --import tsx --test tests/skills/beadboard-driver/skill-local-runner.test.ts && node --import tsx --test tests/skills/beadboard-driver/diagnose-env.test.ts && node --import tsx --test tests/skills/beadboard-driver/heal-common-issues.test.ts && node --import tsx --test tests/lib/epic-graph.test.ts && node --import tsx --test tests/components/shared/left-panel-filtering.test.ts && node --import tsx --test tests/hooks/use-beads-subscription-contract.test.ts && node --import tsx --test tests/components/graph/dependency-graph-hide-closed-contract.test.ts && node --import tsx --test tests/components/shared/unified-shell-hide-closed-contract.test.ts
|
||||||
|
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: bootstrap scaffold files exist
|
||||||
|
ok 1 - bootstrap scaffold files exist
|
||||||
|
---
|
||||||
|
duration_ms: 1.5408
|
||||||
|
...
|
||||||
|
# Subtest: package.json has next/react/typescript scripts and deps
|
||||||
|
ok 2 - package.json has next/react/typescript scripts and deps
|
||||||
|
---
|
||||||
|
duration_ms: 0.6082
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 2
|
||||||
|
# suites 0
|
||||||
|
# pass 2
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 89.6484
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: BaseCard Component Contract
|
||||||
|
# Subtest: exports BaseCard component
|
||||||
|
ok 1 - exports BaseCard component
|
||||||
|
---
|
||||||
|
duration_ms: 48.4903
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 1 - BaseCard Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 49.1763
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: BaseCard Styling Logic
|
||||||
|
# Subtest: should be possible to import the component
|
||||||
|
ok 1 - should be possible to import the component
|
||||||
|
---
|
||||||
|
duration_ms: 1.6433
|
||||||
|
...
|
||||||
|
# Subtest: applies correct status border class for "ready" status
|
||||||
|
ok 2 - applies correct status border class for "ready" status
|
||||||
|
---
|
||||||
|
duration_ms: 8.9211
|
||||||
|
...
|
||||||
|
# Subtest: applies correct status border class for "blocked" status
|
||||||
|
ok 3 - applies correct status border class for "blocked" status
|
||||||
|
---
|
||||||
|
duration_ms: 1.9781
|
||||||
|
...
|
||||||
|
# Subtest: applies selection ring when selected prop is true
|
||||||
|
ok 4 - applies selection ring when selected prop is true
|
||||||
|
---
|
||||||
|
duration_ms: 1.3742
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 2 - BaseCard Styling Logic
|
||||||
|
---
|
||||||
|
duration_ms: 14.3343
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 5
|
||||||
|
# suites 2
|
||||||
|
# pass 5
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 359.7963
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: AgentAvatar Component Contract
|
||||||
|
# Subtest: exports AgentAvatar component
|
||||||
|
ok 1 - exports AgentAvatar component
|
||||||
|
---
|
||||||
|
duration_ms: 112.1114
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 1 - AgentAvatar Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 112.7409
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: AgentAvatar Role Styling
|
||||||
|
# Subtest: applies correct role color class for "ui" role
|
||||||
|
ok 1 - applies correct role color class for "ui" role
|
||||||
|
---
|
||||||
|
duration_ms: 7.7314
|
||||||
|
...
|
||||||
|
# Subtest: applies correct role color class for "orchestrator" role
|
||||||
|
ok 2 - applies correct role color class for "orchestrator" role
|
||||||
|
---
|
||||||
|
duration_ms: 2.3245
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - AgentAvatar Role Styling
|
||||||
|
---
|
||||||
|
duration_ms: 10.3257
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: AgentAvatar ZFC States
|
||||||
|
# Subtest: applies working pulse glow
|
||||||
|
ok 1 - applies working pulse glow
|
||||||
|
---
|
||||||
|
duration_ms: 1.7868
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - AgentAvatar ZFC States
|
||||||
|
---
|
||||||
|
duration_ms: 2.1043
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
# tests 4
|
||||||
|
# suites 3
|
||||||
|
# pass 4
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 414.8522
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SessionsHeader: Agent Grouping
|
||||||
|
# Subtest: groups agents by swarm
|
||||||
|
ok 1 - groups agents by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 2.028
|
||||||
|
...
|
||||||
|
# Subtest: shows fallback bucket for unassigned agents
|
||||||
|
ok 2 - shows fallback bucket for unassigned agents
|
||||||
|
---
|
||||||
|
duration_ms: 0.163
|
||||||
|
...
|
||||||
|
# Subtest: handles empty swarm groups
|
||||||
|
ok 3 - handles empty swarm groups
|
||||||
|
---
|
||||||
|
duration_ms: 0.1271
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 1 - SessionsHeader: Agent Grouping
|
||||||
|
---
|
||||||
|
duration_ms: 3.2566
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
# tests 3
|
||||||
|
# suites 1
|
||||||
|
# pass 3
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 259.5842
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: getAgentRoleColor returns correct color for known roles
|
||||||
|
ok 1 - getAgentRoleColor returns correct color for known roles
|
||||||
|
---
|
||||||
|
duration_ms: 0.7893
|
||||||
|
...
|
||||||
|
# Subtest: getAgentRoleColor returns default for unknown role
|
||||||
|
ok 2 - getAgentRoleColor returns default for unknown role
|
||||||
|
---
|
||||||
|
duration_ms: 0.1416
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 2
|
||||||
|
# suites 0
|
||||||
|
# pass 2
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 260.6046
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: parseIssuesJsonl applies defaults and preserves priority 0
|
||||||
|
ok 1 - parseIssuesJsonl applies defaults and preserves priority 0
|
||||||
|
---
|
||||||
|
duration_ms: 1.1468
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl skips malformed and blank lines
|
||||||
|
ok 2 - parseIssuesJsonl skips malformed and blank lines
|
||||||
|
---
|
||||||
|
duration_ms: 0.2118
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl filters tombstones by default
|
||||||
|
ok 3 - parseIssuesJsonl filters tombstones by default
|
||||||
|
---
|
||||||
|
duration_ms: 0.1758
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl can include tombstones when requested
|
||||||
|
ok 4 - parseIssuesJsonl can include tombstones when requested
|
||||||
|
---
|
||||||
|
duration_ms: 0.1626
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl supports beads dependency schema with depends_on_id and parent-child
|
||||||
|
ok 5 - parseIssuesJsonl supports beads dependency schema with depends_on_id and parent-child
|
||||||
|
---
|
||||||
|
duration_ms: 0.7949
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
# tests 5
|
||||||
|
# suites 0
|
||||||
|
# pass 5
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 269.9367
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: canonicalizeWindowsPath normalizes separators and drive casing
|
||||||
|
ok 1 - canonicalizeWindowsPath normalizes separators and drive casing
|
||||||
|
---
|
||||||
|
duration_ms: 1.4342
|
||||||
|
...
|
||||||
|
# Subtest: windowsPathKey is case-insensitive stable key
|
||||||
|
ok 2 - windowsPathKey is case-insensitive stable key
|
||||||
|
---
|
||||||
|
duration_ms: 0.4737
|
||||||
|
...
|
||||||
|
# Subtest: toDisplayPath renders forward slashes for UI readability
|
||||||
|
ok 3 - toDisplayPath renders forward slashes for UI readability
|
||||||
|
---
|
||||||
|
duration_ms: 0.2287
|
||||||
|
...
|
||||||
|
# Subtest: sameWindowsPath handles case/separator differences
|
||||||
|
ok 4 - sameWindowsPath handles case/separator differences
|
||||||
|
---
|
||||||
|
duration_ms: 0.2269
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
# tests 4
|
||||||
|
# suites 0
|
||||||
|
# pass 4
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 290.6242
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: LeftPanel Component Contract
|
||||||
|
# Subtest: exports LeftPanel component
|
||||||
|
ok 1 - exports LeftPanel component
|
||||||
|
---
|
||||||
|
duration_ms: 291.6468
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel accepts issues and onEpicSelect props
|
||||||
|
ok 2 - LeftPanel accepts issues and onEpicSelect props
|
||||||
|
---
|
||||||
|
duration_ms: 1.3553
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - LeftPanel Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 294.0487
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Tree Structure
|
||||||
|
# Subtest: renders epics as expandable tree items
|
||||||
|
ok 1 - renders epics as expandable tree items
|
||||||
|
---
|
||||||
|
duration_ms: 1.1796
|
||||||
|
...
|
||||||
|
# Subtest: groups beads under their parent epic
|
||||||
|
ok 2 - groups beads under their parent epic
|
||||||
|
---
|
||||||
|
duration_ms: 1.1357
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - LeftPanel Tree Structure
|
||||||
|
---
|
||||||
|
duration_ms: 2.5592
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Responsive Behavior
|
||||||
|
# Subtest: applies responsive classes for desktop, tablet, and mobile
|
||||||
|
ok 1 - applies responsive classes for desktop, tablet, and mobile
|
||||||
|
---
|
||||||
|
duration_ms: 1.4154
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - LeftPanel Responsive Behavior
|
||||||
|
---
|
||||||
|
duration_ms: 1.6082
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Scope Controls
|
||||||
|
# Subtest: renders scope section
|
||||||
|
ok 1 - renders scope section
|
||||||
|
---
|
||||||
|
duration_ms: 1.2414
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 4 - LeftPanel Scope Controls
|
||||||
|
---
|
||||||
|
duration_ms: 1.5705
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
# tests 6
|
||||||
|
# suites 4
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 586.4632
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: TopBar Component Contract
|
||||||
|
# Subtest: exports TopBar component
|
||||||
|
ok 1 - exports TopBar component
|
||||||
|
---
|
||||||
|
duration_ms: 576.3556
|
||||||
|
...
|
||||||
|
# Subtest: TopBar component can be imported without errors
|
||||||
|
ok 2 - TopBar component can be imported without errors
|
||||||
|
---
|
||||||
|
duration_ms: 1.4049
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - TopBar Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 578.6933
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar View Tabs
|
||||||
|
# Subtest: renders view tabs: Social, Graph
|
||||||
|
ok 1 - renders view tabs: Social, Graph
|
||||||
|
---
|
||||||
|
duration_ms: 1.1653
|
||||||
|
...
|
||||||
|
# Subtest: active tab has bold text and accent underline
|
||||||
|
ok 2 - active tab has bold text and accent underline
|
||||||
|
---
|
||||||
|
duration_ms: 1.0756
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - TopBar View Tabs
|
||||||
|
---
|
||||||
|
duration_ms: 2.4564
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar Filter and Controls
|
||||||
|
# Subtest: renders filter/search input placeholder
|
||||||
|
ok 1 - renders filter/search input placeholder
|
||||||
|
---
|
||||||
|
duration_ms: 1.2466
|
||||||
|
...
|
||||||
|
# Subtest: renders settings placeholder
|
||||||
|
ok 2 - renders settings placeholder
|
||||||
|
---
|
||||||
|
duration_ms: 1.0997
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 3 - TopBar Filter and Controls
|
||||||
|
---
|
||||||
|
duration_ms: 2.7634
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
# tests 6
|
||||||
|
# suites 3
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 843.1609
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: Mobile Navigation - Hamburger Menu
|
||||||
|
# Subtest: exports MobileNav component
|
||||||
|
ok 1 - exports MobileNav component
|
||||||
|
---
|
||||||
|
duration_ms: 122.7061
|
||||||
|
...
|
||||||
|
# Subtest: renders tab buttons: Social, Graph
|
||||||
|
ok 2 - renders tab buttons: Social, Graph
|
||||||
|
---
|
||||||
|
duration_ms: 1.5081
|
||||||
|
...
|
||||||
|
# Subtest: highlights active tab with accent color
|
||||||
|
ok 3 - highlights active tab with accent color
|
||||||
|
---
|
||||||
|
duration_ms: 1.5626
|
||||||
|
...
|
||||||
|
# Subtest: uses setView from useUrlState on tab click
|
||||||
|
ok 4 - uses setView from useUrlState on tab click
|
||||||
|
---
|
||||||
|
duration_ms: 1.3467
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 1 - Mobile Navigation - Hamburger Menu
|
||||||
|
---
|
||||||
|
duration_ms: 128.9482
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar Hamburger Menu
|
||||||
|
# Subtest: shows hamburger button on mobile and tablet
|
||||||
|
ok 1 - shows hamburger button on mobile and tablet
|
||||||
|
---
|
||||||
|
duration_ms: 487.0038
|
||||||
|
...
|
||||||
|
# Subtest: hamburger button opens left panel drawer
|
||||||
|
ok 2 - hamburger button opens left panel drawer
|
||||||
|
---
|
||||||
|
duration_ms: 1.478
|
||||||
|
...
|
||||||
|
# Subtest: hides hamburger on desktop
|
||||||
|
ok 3 - hides hamburger on desktop
|
||||||
|
---
|
||||||
|
duration_ms: 1.1051
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 2 - TopBar Hamburger Menu
|
||||||
|
---
|
||||||
|
duration_ms: 490.1177
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 7
|
||||||
|
# suites 2
|
||||||
|
# pass 7
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 899.3239
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SwarmCard Component Contract
|
||||||
|
# Subtest: exports SwarmCard component
|
||||||
|
ok 1 - exports SwarmCard component
|
||||||
|
---
|
||||||
|
duration_ms: 238.5797
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard component can be imported without errors
|
||||||
|
ok 2 - SwarmCard component can be imported without errors
|
||||||
|
---
|
||||||
|
duration_ms: 1.6861
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - SwarmCard Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 241.2828
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Agent Roster
|
||||||
|
# Subtest: renders agent avatars with liveness glow
|
||||||
|
ok 1 - renders agent avatars with liveness glow
|
||||||
|
---
|
||||||
|
duration_ms: 1.7451
|
||||||
|
...
|
||||||
|
# Subtest: displays agent current task when available
|
||||||
|
ok 2 - displays agent current task when available
|
||||||
|
---
|
||||||
|
duration_ms: 1.4825
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - SwarmCard Agent Roster
|
||||||
|
---
|
||||||
|
duration_ms: 3.5718
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Progress Bar
|
||||||
|
# Subtest: renders progress bar showing completion percentage
|
||||||
|
ok 1 - renders progress bar showing completion percentage
|
||||||
|
---
|
||||||
|
duration_ms: 1.6795
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - SwarmCard Progress Bar
|
||||||
|
---
|
||||||
|
duration_ms: 1.8984
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Attention Items
|
||||||
|
# Subtest: renders attention items with warning styling
|
||||||
|
ok 1 - renders attention items with warning styling
|
||||||
|
---
|
||||||
|
duration_ms: 1.7077
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 4 - SwarmCard Attention Items
|
||||||
|
---
|
||||||
|
duration_ms: 2.1567
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard View-Jump Icons
|
||||||
|
# Subtest: renders view-jump icons for navigation
|
||||||
|
ok 1 - renders view-jump icons for navigation
|
||||||
|
---
|
||||||
|
duration_ms: 1.7431
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 5 - SwarmCard View-Jump Icons
|
||||||
|
---
|
||||||
|
duration_ms: 2.0339
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
# tests 7
|
||||||
|
# suites 5
|
||||||
|
# pass 7
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 515.754
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: URL State Integration - bb-ui2.22
|
||||||
|
# Subtest: Valid URL Patterns - Social View
|
||||||
|
# Subtest: /?view=social - defaults to social view
|
||||||
|
ok 1 - /?view=social - defaults to social view
|
||||||
|
---
|
||||||
|
duration_ms: 0.8379
|
||||||
|
...
|
||||||
|
# Subtest: /?view=social&task=bb-buff.1&panel=open - task selected, panel open
|
||||||
|
ok 2 - /?view=social&task=bb-buff.1&panel=open - task selected, panel open
|
||||||
|
---
|
||||||
|
duration_ms: 0.2068
|
||||||
|
...
|
||||||
|
# Subtest: /?view=social&task=bb-ui2.22 - task with dots in ID
|
||||||
|
ok 3 - /?view=social&task=bb-ui2.22 - task with dots in ID
|
||||||
|
---
|
||||||
|
duration_ms: 0.1241
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 1 - Valid URL Patterns - Social View
|
||||||
|
---
|
||||||
|
duration_ms: 1.803
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Valid URL Patterns - Graph View
|
||||||
|
# Subtest: /?view=graph - graph view default
|
||||||
|
not ok 1 - /?view=graph - graph view default
|
||||||
|
---
|
||||||
|
duration_ms: 3.2799
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:2578'
|
||||||
|
failureType: 'testCodeFailure'
|
||||||
|
error: |-
|
||||||
|
Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'overview' !== 'flow'
|
||||||
|
|
||||||
|
code: 'ERR_ASSERTION'
|
||||||
|
name: 'AssertionError'
|
||||||
|
expected: 'flow'
|
||||||
|
actual: 'overview'
|
||||||
|
operator: 'strictEqual'
|
||||||
|
stack: |-
|
||||||
|
TestContext.<anonymous> (C:\Users\Zenchant\codex\beadboard\tests\hooks\url-state-integration.test.ts:53:14)
|
||||||
|
Test.runInAsyncScope (node:async_hooks:211:14)
|
||||||
|
Test.run (node:internal/test_runner/test:934:25)
|
||||||
|
Test.start (node:internal/test_runner/test:833:17)
|
||||||
|
node:internal/test_runner/test:1318:71
|
||||||
|
node:internal/per_context/primordials:483:82
|
||||||
|
new Promise (<anonymous>)
|
||||||
|
new SafePromise (node:internal/per_context/primordials:451:29)
|
||||||
|
node:internal/per_context/primordials:483:9
|
||||||
|
Array.map (<anonymous>)
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&task=bb-buff.1 - graph with task selected
|
||||||
|
ok 2 - /?view=graph&task=bb-buff.1 - graph with task selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.1241
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=flow - flow tab selected
|
||||||
|
ok 3 - /?view=graph&graphTab=flow - flow tab selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.2827
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=overview - overview tab selected
|
||||||
|
ok 4 - /?view=graph&graphTab=overview - overview tab selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.1152
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&swarm=bb-buff - graph filtered by swarm
|
||||||
|
ok 5 - /?view=graph&swarm=bb-buff - graph filtered by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 0.3114
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
not ok 2 - Valid URL Patterns - Graph View
|
||||||
|
---
|
||||||
|
duration_ms: 4.4826
|
||||||
|
type: 'suite'
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:2515'
|
||||||
|
failureType: 'subtestsFailed'
|
||||||
|
error: '1 subtest failed'
|
||||||
|
code: 'ERR_TEST_FAILURE'
|
||||||
|
...
|
||||||
|
# Subtest: Deprecated Swarm View Fallback
|
||||||
|
# Subtest: /?view=swarm - falls back to social (swarm view deprecated)
|
||||||
|
ok 1 - /?view=swarm - falls back to social (swarm view deprecated)
|
||||||
|
---
|
||||||
|
duration_ms: 0.176
|
||||||
|
...
|
||||||
|
# Subtest: /?view=swarm&swarm=bb-buff - falls back to social but preserves swarmId
|
||||||
|
ok 2 - /?view=swarm&swarm=bb-buff - falls back to social but preserves swarmId
|
||||||
|
---
|
||||||
|
duration_ms: 0.3375
|
||||||
|
...
|
||||||
|
# Subtest: /?view=swarm&swarm=bb-buff&panel=open - falls back to social with panel open
|
||||||
|
ok 3 - /?view=swarm&swarm=bb-buff&panel=open - falls back to social with panel open
|
||||||
|
---
|
||||||
|
duration_ms: 0.1203
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 3 - Deprecated Swarm View Fallback
|
||||||
|
---
|
||||||
|
duration_ms: 0.8018
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Valid URL Patterns - Activity View
|
||||||
|
# Subtest: /?view=activity - activity view default
|
||||||
|
ok 1 - /?view=activity - activity view default
|
||||||
|
---
|
||||||
|
duration_ms: 0.1469
|
||||||
|
...
|
||||||
|
# Subtest: /?view=activity&agent=bb-silver-castle - filtered by agent
|
||||||
|
ok 2 - /?view=activity&agent=bb-silver-castle - filtered by agent
|
||||||
|
---
|
||||||
|
duration_ms: 0.0856
|
||||||
|
...
|
||||||
|
# Subtest: /?view=activity&swarm=bb-buff - filtered by swarm
|
||||||
|
ok 3 - /?view=activity&swarm=bb-buff - filtered by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 0.0813
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 4 - Valid URL Patterns - Activity View
|
||||||
|
---
|
||||||
|
duration_ms: 0.4565
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Invalid Param Handling
|
||||||
|
# Subtest: /?view=invalid - invalid view defaults to social
|
||||||
|
ok 1 - /?view=invalid - invalid view defaults to social
|
||||||
|
---
|
||||||
|
duration_ms: 0.1489
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=invalid - invalid graphTab defaults to flow
|
||||||
|
not ok 2 - /?view=graph&graphTab=invalid - invalid graphTab defaults to flow
|
||||||
|
---
|
||||||
|
duration_ms: 0.4762
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:6479'
|
||||||
|
failureType: 'testCodeFailure'
|
||||||
|
error: |-
|
||||||
|
Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'overview' !== 'flow'
|
||||||
|
|
||||||
|
code: 'ERR_ASSERTION'
|
||||||
|
name: 'AssertionError'
|
||||||
|
expected: 'flow'
|
||||||
|
actual: 'overview'
|
||||||
|
operator: 'strictEqual'
|
||||||
|
stack: |-
|
||||||
|
TestContext.<anonymous> (C:\Users\Zenchant\codex\beadboard\tests\hooks\url-state-integration.test.ts:138:14)
|
||||||
|
Test.runInAsyncScope (node:async_hooks:211:14)
|
||||||
|
Test.run (node:internal/test_runner/test:934:25)
|
||||||
|
Suite.processPendingSubtests (node:internal/test_runner/test:633:18)
|
||||||
|
Test.postRun (node:internal/test_runner/test:1045:19)
|
||||||
|
Test.run (node:internal/test_runner/test:973:12)
|
||||||
|
async Promise.all (index 0)
|
||||||
|
async Suite.run (node:internal/test_runner/test:1320:7)
|
||||||
|
async Suite.processPendingSubtests (node:internal/test_runner/test:633:7)
|
||||||
|
...
|
||||||
|
# Subtest: /?panel=invalid - invalid panel defaults to open
|
||||||
|
ok 3 - /?panel=invalid - invalid panel defaults to open
|
||||||
|
---
|
||||||
|
duration_ms: 0.1006
|
||||||
|
...
|
||||||
|
# Subtest: /?task=invalid-id - invalid task ID still parsed (no validation)
|
||||||
|
ok 4 - /?task=invalid-id - invalid task ID still parsed (no validation)
|
||||||
|
---
|
||||||
|
duration_ms: 0.0841
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
not ok 5 - Invalid Param Handling
|
||||||
|
---
|
||||||
|
duration_ms: 0.9701
|
||||||
|
type: 'suite'
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:6178'
|
||||||
|
failureType: 'subtestsFailed'
|
||||||
|
error: '1 subtest failed'
|
||||||
|
code: 'ERR_TEST_FAILURE'
|
||||||
|
...
|
||||||
|
# Subtest: URL Building - State to URL
|
||||||
|
# Subtest: builds social view URL
|
||||||
|
ok 1 - builds social view URL
|
||||||
|
---
|
||||||
|
duration_ms: 0.2194
|
||||||
|
...
|
||||||
|
# Subtest: builds graph view with task URL
|
||||||
|
ok 2 - builds graph view with task URL
|
||||||
|
---
|
||||||
|
duration_ms: 0.0932
|
||||||
|
...
|
||||||
|
# Subtest: builds swarm view with swarm param
|
||||||
|
ok 3 - builds swarm view with swarm param
|
||||||
|
---
|
||||||
|
duration_ms: 0.085
|
||||||
|
...
|
||||||
|
# Subtest: builds activity view with agent filter
|
||||||
|
ok 4 - builds activity view with agent filter
|
||||||
|
---
|
||||||
|
duration_ms: 0.1405
|
||||||
|
...
|
||||||
|
# Subtest: preserves existing params when adding new ones
|
||||||
|
ok 5 - preserves existing params when adding new ones
|
||||||
|
---
|
||||||
|
duration_ms: 0.1868
|
||||||
|
...
|
||||||
|
# Subtest: removes params when set to null
|
||||||
|
ok 6 - removes params when set to null
|
||||||
|
---
|
||||||
|
duration_ms: 0.1154
|
||||||
|
...
|
||||||
|
# Subtest: returns root when all params cleared
|
||||||
|
ok 7 - returns root when all params cleared
|
||||||
|
---
|
||||||
|
duration_ms: 0.0926
|
||||||
|
...
|
||||||
|
1..7
|
||||||
|
ok 6 - URL Building - State to URL
|
||||||
|
---
|
||||||
|
duration_ms: 1.1103
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Complex URL Scenarios
|
||||||
|
# Subtest: handles all params together
|
||||||
|
ok 1 - handles all params together
|
||||||
|
---
|
||||||
|
duration_ms: 0.123
|
||||||
|
...
|
||||||
|
# Subtest: empty string values treated as null/empty
|
||||||
|
ok 2 - empty string values treated as null/empty
|
||||||
|
---
|
||||||
|
duration_ms: 0.0893
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 7 - Complex URL Scenarios
|
||||||
|
---
|
||||||
|
duration_ms: 0.351
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Deep Link Patterns - From Card Icons
|
||||||
|
# Subtest: SocialCard Graph icon: /?view=graph&task={id}
|
||||||
|
ok 1 - SocialCard Graph icon: /?view=graph&task={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.1287
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Graph icon: /?view=graph&swarm={id}
|
||||||
|
ok 2 - SwarmCard Graph icon: /?view=graph&swarm={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.0785
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Timeline icon: /?view=activity&swarm={id}
|
||||||
|
ok 3 - SwarmCard Timeline icon: /?view=activity&swarm={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.1411
|
||||||
|
...
|
||||||
|
# Subtest: Agent avatar click: /?view=activity&agent={id}
|
||||||
|
ok 4 - Agent avatar click: /?view=activity&agent={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.076
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 8 - Deep Link Patterns - From Card Icons
|
||||||
|
---
|
||||||
|
duration_ms: 0.5678
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..8
|
||||||
|
not ok 1 - URL State Integration - bb-ui2.22
|
||||||
|
---
|
||||||
|
duration_ms: 11.4418
|
||||||
|
type: 'suite'
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:1269'
|
||||||
|
failureType: 'subtestsFailed'
|
||||||
|
error: '2 subtests failed'
|
||||||
|
code: 'ERR_TEST_FAILURE'
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
# tests 31
|
||||||
|
# suites 9
|
||||||
|
# pass 29
|
||||||
|
# fail 2
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 447.3716
|
||||||
919
.beads/npm_test_output2.txt
Normal file
919
.beads/npm_test_output2.txt
Normal file
|
|
@ -0,0 +1,919 @@
|
||||||
|
|
||||||
|
> beadboard@0.1.0 test
|
||||||
|
> node --test tests/bootstrap.test.mjs && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/blocked-triage-modal.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/components/graph/graph-node-conversation.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/install-manifest.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx && node --import tsx --test tests/scripts/beadboard-launcher.test.ts && node --import tsx --test tests/scripts/install-wrappers-contract.test.ts && node --import tsx --test tests/scripts/install-sh-smoke.test.ts && node --import tsx --test tests/scripts/installer-ci-contract.test.ts && node --import tsx --test tests/docs/installer-quickstart-contract.test.ts && node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts && node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts && node --import tsx --test tests/skills/beadboard-driver/generate-agent-name.test.ts && node --import tsx --test tests/skills/beadboard-driver/readiness-report.test.ts && node --import tsx --test tests/skills/beadboard-driver/skill-local-runner.test.ts && node --import tsx --test tests/skills/beadboard-driver/diagnose-env.test.ts && node --import tsx --test tests/skills/beadboard-driver/heal-common-issues.test.ts && node --import tsx --test tests/lib/epic-graph.test.ts && node --import tsx --test tests/components/shared/left-panel-filtering.test.ts && node --import tsx --test tests/hooks/use-beads-subscription-contract.test.ts && node --import tsx --test tests/components/graph/dependency-graph-hide-closed-contract.test.ts && node --import tsx --test tests/components/shared/unified-shell-hide-closed-contract.test.ts
|
||||||
|
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: bootstrap scaffold files exist
|
||||||
|
ok 1 - bootstrap scaffold files exist
|
||||||
|
---
|
||||||
|
duration_ms: 2.2085
|
||||||
|
...
|
||||||
|
# Subtest: package.json has next/react/typescript scripts and deps
|
||||||
|
ok 2 - package.json has next/react/typescript scripts and deps
|
||||||
|
---
|
||||||
|
duration_ms: 0.5581
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 2
|
||||||
|
# suites 0
|
||||||
|
# pass 2
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 140.1608
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: BaseCard Component Contract
|
||||||
|
# Subtest: exports BaseCard component
|
||||||
|
ok 1 - exports BaseCard component
|
||||||
|
---
|
||||||
|
duration_ms: 58.211
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 1 - BaseCard Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 58.9965
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: BaseCard Styling Logic
|
||||||
|
# Subtest: should be possible to import the component
|
||||||
|
ok 1 - should be possible to import the component
|
||||||
|
---
|
||||||
|
duration_ms: 2.4528
|
||||||
|
...
|
||||||
|
# Subtest: applies correct status border class for "ready" status
|
||||||
|
ok 2 - applies correct status border class for "ready" status
|
||||||
|
---
|
||||||
|
duration_ms: 10.367
|
||||||
|
...
|
||||||
|
# Subtest: applies correct status border class for "blocked" status
|
||||||
|
ok 3 - applies correct status border class for "blocked" status
|
||||||
|
---
|
||||||
|
duration_ms: 2.0545
|
||||||
|
...
|
||||||
|
# Subtest: applies selection ring when selected prop is true
|
||||||
|
ok 4 - applies selection ring when selected prop is true
|
||||||
|
---
|
||||||
|
duration_ms: 1.3688
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 2 - BaseCard Styling Logic
|
||||||
|
---
|
||||||
|
duration_ms: 16.6551
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 5
|
||||||
|
# suites 2
|
||||||
|
# pass 5
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 447.2767
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: AgentAvatar Component Contract
|
||||||
|
# Subtest: exports AgentAvatar component
|
||||||
|
ok 1 - exports AgentAvatar component
|
||||||
|
---
|
||||||
|
duration_ms: 154.6347
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 1 - AgentAvatar Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 155.6009
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: AgentAvatar Role Styling
|
||||||
|
# Subtest: applies correct role color class for "ui" role
|
||||||
|
ok 1 - applies correct role color class for "ui" role
|
||||||
|
---
|
||||||
|
duration_ms: 11.7114
|
||||||
|
...
|
||||||
|
# Subtest: applies correct role color class for "orchestrator" role
|
||||||
|
ok 2 - applies correct role color class for "orchestrator" role
|
||||||
|
---
|
||||||
|
duration_ms: 2.6704
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - AgentAvatar Role Styling
|
||||||
|
---
|
||||||
|
duration_ms: 14.7057
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: AgentAvatar ZFC States
|
||||||
|
# Subtest: applies working pulse glow
|
||||||
|
ok 1 - applies working pulse glow
|
||||||
|
---
|
||||||
|
duration_ms: 2.4504
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - AgentAvatar ZFC States
|
||||||
|
---
|
||||||
|
duration_ms: 2.8198
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
# tests 4
|
||||||
|
# suites 3
|
||||||
|
# pass 4
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 539.4517
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SessionsHeader: Agent Grouping
|
||||||
|
# Subtest: groups agents by swarm
|
||||||
|
ok 1 - groups agents by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 2.114
|
||||||
|
...
|
||||||
|
# Subtest: shows fallback bucket for unassigned agents
|
||||||
|
ok 2 - shows fallback bucket for unassigned agents
|
||||||
|
---
|
||||||
|
duration_ms: 0.1808
|
||||||
|
...
|
||||||
|
# Subtest: handles empty swarm groups
|
||||||
|
ok 3 - handles empty swarm groups
|
||||||
|
---
|
||||||
|
duration_ms: 0.1339
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 1 - SessionsHeader: Agent Grouping
|
||||||
|
---
|
||||||
|
duration_ms: 3.4059
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
# tests 3
|
||||||
|
# suites 1
|
||||||
|
# pass 3
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 283.1807
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: getAgentRoleColor returns correct color for known roles
|
||||||
|
ok 1 - getAgentRoleColor returns correct color for known roles
|
||||||
|
---
|
||||||
|
duration_ms: 0.8966
|
||||||
|
...
|
||||||
|
# Subtest: getAgentRoleColor returns default for unknown role
|
||||||
|
ok 2 - getAgentRoleColor returns default for unknown role
|
||||||
|
---
|
||||||
|
duration_ms: 0.1598
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 2
|
||||||
|
# suites 0
|
||||||
|
# pass 2
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 309.895
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: parseIssuesJsonl applies defaults and preserves priority 0
|
||||||
|
ok 1 - parseIssuesJsonl applies defaults and preserves priority 0
|
||||||
|
---
|
||||||
|
duration_ms: 1.1101
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl skips malformed and blank lines
|
||||||
|
ok 2 - parseIssuesJsonl skips malformed and blank lines
|
||||||
|
---
|
||||||
|
duration_ms: 0.1784
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl filters tombstones by default
|
||||||
|
ok 3 - parseIssuesJsonl filters tombstones by default
|
||||||
|
---
|
||||||
|
duration_ms: 0.1475
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl can include tombstones when requested
|
||||||
|
ok 4 - parseIssuesJsonl can include tombstones when requested
|
||||||
|
---
|
||||||
|
duration_ms: 0.1428
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl supports beads dependency schema with depends_on_id and parent-child
|
||||||
|
ok 5 - parseIssuesJsonl supports beads dependency schema with depends_on_id and parent-child
|
||||||
|
---
|
||||||
|
duration_ms: 0.7034
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
# tests 5
|
||||||
|
# suites 0
|
||||||
|
# pass 5
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 279.9641
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: canonicalizeWindowsPath normalizes separators and drive casing
|
||||||
|
ok 1 - canonicalizeWindowsPath normalizes separators and drive casing
|
||||||
|
---
|
||||||
|
duration_ms: 0.9528
|
||||||
|
...
|
||||||
|
# Subtest: windowsPathKey is case-insensitive stable key
|
||||||
|
ok 2 - windowsPathKey is case-insensitive stable key
|
||||||
|
---
|
||||||
|
duration_ms: 0.2036
|
||||||
|
...
|
||||||
|
# Subtest: toDisplayPath renders forward slashes for UI readability
|
||||||
|
ok 3 - toDisplayPath renders forward slashes for UI readability
|
||||||
|
---
|
||||||
|
duration_ms: 0.1337
|
||||||
|
...
|
||||||
|
# Subtest: sameWindowsPath handles case/separator differences
|
||||||
|
ok 4 - sameWindowsPath handles case/separator differences
|
||||||
|
---
|
||||||
|
duration_ms: 0.1696
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
# tests 4
|
||||||
|
# suites 0
|
||||||
|
# pass 4
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 300.62
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: LeftPanel Component Contract
|
||||||
|
# Subtest: exports LeftPanel component
|
||||||
|
ok 1 - exports LeftPanel component
|
||||||
|
---
|
||||||
|
duration_ms: 306.6047
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel accepts issues and onEpicSelect props
|
||||||
|
ok 2 - LeftPanel accepts issues and onEpicSelect props
|
||||||
|
---
|
||||||
|
duration_ms: 1.3879
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - LeftPanel Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 309.0712
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Tree Structure
|
||||||
|
# Subtest: renders epics as expandable tree items
|
||||||
|
ok 1 - renders epics as expandable tree items
|
||||||
|
---
|
||||||
|
duration_ms: 1.345
|
||||||
|
...
|
||||||
|
# Subtest: groups beads under their parent epic
|
||||||
|
ok 2 - groups beads under their parent epic
|
||||||
|
---
|
||||||
|
duration_ms: 1.7164
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - LeftPanel Tree Structure
|
||||||
|
---
|
||||||
|
duration_ms: 3.332
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Responsive Behavior
|
||||||
|
# Subtest: applies responsive classes for desktop, tablet, and mobile
|
||||||
|
ok 1 - applies responsive classes for desktop, tablet, and mobile
|
||||||
|
---
|
||||||
|
duration_ms: 1.3508
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - LeftPanel Responsive Behavior
|
||||||
|
---
|
||||||
|
duration_ms: 1.5231
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Scope Controls
|
||||||
|
# Subtest: renders scope section
|
||||||
|
ok 1 - renders scope section
|
||||||
|
---
|
||||||
|
duration_ms: 1.3167
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 4 - LeftPanel Scope Controls
|
||||||
|
---
|
||||||
|
duration_ms: 1.7059
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
# tests 6
|
||||||
|
# suites 4
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 598.9805
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: TopBar Component Contract
|
||||||
|
# Subtest: exports TopBar component
|
||||||
|
ok 1 - exports TopBar component
|
||||||
|
---
|
||||||
|
duration_ms: 683.336
|
||||||
|
...
|
||||||
|
# Subtest: TopBar component can be imported without errors
|
||||||
|
ok 2 - TopBar component can be imported without errors
|
||||||
|
---
|
||||||
|
duration_ms: 1.848
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - TopBar Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 686.5443
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar View Tabs
|
||||||
|
# Subtest: renders view tabs: Social, Graph
|
||||||
|
ok 1 - renders view tabs: Social, Graph
|
||||||
|
---
|
||||||
|
duration_ms: 1.4361
|
||||||
|
...
|
||||||
|
# Subtest: active tab has bold text and accent underline
|
||||||
|
ok 2 - active tab has bold text and accent underline
|
||||||
|
---
|
||||||
|
duration_ms: 1.6179
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - TopBar View Tabs
|
||||||
|
---
|
||||||
|
duration_ms: 3.3478
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar Filter and Controls
|
||||||
|
# Subtest: renders filter/search input placeholder
|
||||||
|
ok 1 - renders filter/search input placeholder
|
||||||
|
---
|
||||||
|
duration_ms: 1.7411
|
||||||
|
...
|
||||||
|
# Subtest: renders settings placeholder
|
||||||
|
ok 2 - renders settings placeholder
|
||||||
|
---
|
||||||
|
duration_ms: 1.2955
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 3 - TopBar Filter and Controls
|
||||||
|
---
|
||||||
|
duration_ms: 3.5576
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
# tests 6
|
||||||
|
# suites 3
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 991.5175
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: Mobile Navigation - Hamburger Menu
|
||||||
|
# Subtest: exports MobileNav component
|
||||||
|
ok 1 - exports MobileNav component
|
||||||
|
---
|
||||||
|
duration_ms: 160.2862
|
||||||
|
...
|
||||||
|
# Subtest: renders tab buttons: Social, Graph
|
||||||
|
ok 2 - renders tab buttons: Social, Graph
|
||||||
|
---
|
||||||
|
duration_ms: 1.5794
|
||||||
|
...
|
||||||
|
# Subtest: highlights active tab with accent color
|
||||||
|
ok 3 - highlights active tab with accent color
|
||||||
|
---
|
||||||
|
duration_ms: 1.2026
|
||||||
|
...
|
||||||
|
# Subtest: uses setView from useUrlState on tab click
|
||||||
|
ok 4 - uses setView from useUrlState on tab click
|
||||||
|
---
|
||||||
|
duration_ms: 1.1548
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 1 - Mobile Navigation - Hamburger Menu
|
||||||
|
---
|
||||||
|
duration_ms: 166.2933
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar Hamburger Menu
|
||||||
|
# Subtest: shows hamburger button on mobile and tablet
|
||||||
|
ok 1 - shows hamburger button on mobile and tablet
|
||||||
|
---
|
||||||
|
duration_ms: 520.9119
|
||||||
|
...
|
||||||
|
# Subtest: hamburger button opens left panel drawer
|
||||||
|
ok 2 - hamburger button opens left panel drawer
|
||||||
|
---
|
||||||
|
duration_ms: 1.9043
|
||||||
|
...
|
||||||
|
# Subtest: hides hamburger on desktop
|
||||||
|
ok 3 - hides hamburger on desktop
|
||||||
|
---
|
||||||
|
duration_ms: 1.566
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 2 - TopBar Hamburger Menu
|
||||||
|
---
|
||||||
|
duration_ms: 525.0916
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 7
|
||||||
|
# suites 2
|
||||||
|
# pass 7
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 1031.056
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SwarmCard Component Contract
|
||||||
|
# Subtest: exports SwarmCard component
|
||||||
|
ok 1 - exports SwarmCard component
|
||||||
|
---
|
||||||
|
duration_ms: 263.9758
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard component can be imported without errors
|
||||||
|
ok 2 - SwarmCard component can be imported without errors
|
||||||
|
---
|
||||||
|
duration_ms: 1.4629
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - SwarmCard Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 266.6715
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Agent Roster
|
||||||
|
# Subtest: renders agent avatars with liveness glow
|
||||||
|
ok 1 - renders agent avatars with liveness glow
|
||||||
|
---
|
||||||
|
duration_ms: 1.3692
|
||||||
|
...
|
||||||
|
# Subtest: displays agent current task when available
|
||||||
|
ok 2 - displays agent current task when available
|
||||||
|
---
|
||||||
|
duration_ms: 1.5995
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - SwarmCard Agent Roster
|
||||||
|
---
|
||||||
|
duration_ms: 3.3144
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Progress Bar
|
||||||
|
# Subtest: renders progress bar showing completion percentage
|
||||||
|
ok 1 - renders progress bar showing completion percentage
|
||||||
|
---
|
||||||
|
duration_ms: 1.8139
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - SwarmCard Progress Bar
|
||||||
|
---
|
||||||
|
duration_ms: 2.0726
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Attention Items
|
||||||
|
# Subtest: renders attention items with warning styling
|
||||||
|
ok 1 - renders attention items with warning styling
|
||||||
|
---
|
||||||
|
duration_ms: 1.8572
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 4 - SwarmCard Attention Items
|
||||||
|
---
|
||||||
|
duration_ms: 2.3816
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard View-Jump Icons
|
||||||
|
# Subtest: renders view-jump icons for navigation
|
||||||
|
ok 1 - renders view-jump icons for navigation
|
||||||
|
---
|
||||||
|
duration_ms: 1.7362
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 5 - SwarmCard View-Jump Icons
|
||||||
|
---
|
||||||
|
duration_ms: 1.9907
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
# tests 7
|
||||||
|
# suites 5
|
||||||
|
# pass 7
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 586.4929
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: URL State Integration - bb-ui2.22
|
||||||
|
# Subtest: Valid URL Patterns - Social View
|
||||||
|
# Subtest: /?view=social - defaults to social view
|
||||||
|
ok 1 - /?view=social - defaults to social view
|
||||||
|
---
|
||||||
|
duration_ms: 0.8523
|
||||||
|
...
|
||||||
|
# Subtest: /?view=social&task=bb-buff.1&panel=open - task selected, panel open
|
||||||
|
ok 2 - /?view=social&task=bb-buff.1&panel=open - task selected, panel open
|
||||||
|
---
|
||||||
|
duration_ms: 0.2088
|
||||||
|
...
|
||||||
|
# Subtest: /?view=social&task=bb-ui2.22 - task with dots in ID
|
||||||
|
ok 3 - /?view=social&task=bb-ui2.22 - task with dots in ID
|
||||||
|
---
|
||||||
|
duration_ms: 0.1511
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 1 - Valid URL Patterns - Social View
|
||||||
|
---
|
||||||
|
duration_ms: 1.865
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Valid URL Patterns - Graph View
|
||||||
|
# Subtest: /?view=graph - graph view default
|
||||||
|
ok 1 - /?view=graph - graph view default
|
||||||
|
---
|
||||||
|
duration_ms: 0.2128
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&task=bb-buff.1 - graph with task selected
|
||||||
|
ok 2 - /?view=graph&task=bb-buff.1 - graph with task selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.1196
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=flow - flow tab selected
|
||||||
|
ok 3 - /?view=graph&graphTab=flow - flow tab selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.1573
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=overview - overview tab selected
|
||||||
|
ok 4 - /?view=graph&graphTab=overview - overview tab selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.116
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&swarm=bb-buff - graph filtered by swarm
|
||||||
|
ok 5 - /?view=graph&swarm=bb-buff - graph filtered by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 0.2221
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
ok 2 - Valid URL Patterns - Graph View
|
||||||
|
---
|
||||||
|
duration_ms: 1.1579
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Deprecated Swarm View Fallback
|
||||||
|
# Subtest: /?view=swarm - falls back to social (swarm view deprecated)
|
||||||
|
ok 1 - /?view=swarm - falls back to social (swarm view deprecated)
|
||||||
|
---
|
||||||
|
duration_ms: 0.7522
|
||||||
|
...
|
||||||
|
# Subtest: /?view=swarm&swarm=bb-buff - falls back to social but preserves swarmId
|
||||||
|
ok 2 - /?view=swarm&swarm=bb-buff - falls back to social but preserves swarmId
|
||||||
|
---
|
||||||
|
duration_ms: 0.2523
|
||||||
|
...
|
||||||
|
# Subtest: /?view=swarm&swarm=bb-buff&panel=open - falls back to social with panel open
|
||||||
|
ok 3 - /?view=swarm&swarm=bb-buff&panel=open - falls back to social with panel open
|
||||||
|
---
|
||||||
|
duration_ms: 0.1228
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 3 - Deprecated Swarm View Fallback
|
||||||
|
---
|
||||||
|
duration_ms: 1.276
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Valid URL Patterns - Activity View
|
||||||
|
# Subtest: /?view=activity - activity view default
|
||||||
|
ok 1 - /?view=activity - activity view default
|
||||||
|
---
|
||||||
|
duration_ms: 0.1366
|
||||||
|
...
|
||||||
|
# Subtest: /?view=activity&agent=bb-silver-castle - filtered by agent
|
||||||
|
ok 2 - /?view=activity&agent=bb-silver-castle - filtered by agent
|
||||||
|
---
|
||||||
|
duration_ms: 0.0823
|
||||||
|
...
|
||||||
|
# Subtest: /?view=activity&swarm=bb-buff - filtered by swarm
|
||||||
|
ok 3 - /?view=activity&swarm=bb-buff - filtered by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 0.0788
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 4 - Valid URL Patterns - Activity View
|
||||||
|
---
|
||||||
|
duration_ms: 0.4389
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Invalid Param Handling
|
||||||
|
# Subtest: /?view=invalid - invalid view defaults to social
|
||||||
|
ok 1 - /?view=invalid - invalid view defaults to social
|
||||||
|
---
|
||||||
|
duration_ms: 0.1141
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=invalid - invalid graphTab defaults to flow
|
||||||
|
ok 2 - /?view=graph&graphTab=invalid - invalid graphTab defaults to flow
|
||||||
|
---
|
||||||
|
duration_ms: 0.0807
|
||||||
|
...
|
||||||
|
# Subtest: /?panel=invalid - invalid panel defaults to open
|
||||||
|
ok 3 - /?panel=invalid - invalid panel defaults to open
|
||||||
|
---
|
||||||
|
duration_ms: 0.0801
|
||||||
|
...
|
||||||
|
# Subtest: /?task=invalid-id - invalid task ID still parsed (no validation)
|
||||||
|
ok 4 - /?task=invalid-id - invalid task ID still parsed (no validation)
|
||||||
|
---
|
||||||
|
duration_ms: 0.086
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 5 - Invalid Param Handling
|
||||||
|
---
|
||||||
|
duration_ms: 0.4767
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: URL Building - State to URL
|
||||||
|
# Subtest: builds social view URL
|
||||||
|
ok 1 - builds social view URL
|
||||||
|
---
|
||||||
|
duration_ms: 0.2155
|
||||||
|
...
|
||||||
|
# Subtest: builds graph view with task URL
|
||||||
|
ok 2 - builds graph view with task URL
|
||||||
|
---
|
||||||
|
duration_ms: 0.1448
|
||||||
|
...
|
||||||
|
# Subtest: builds swarm view with swarm param
|
||||||
|
ok 3 - builds swarm view with swarm param
|
||||||
|
---
|
||||||
|
duration_ms: 0.0849
|
||||||
|
...
|
||||||
|
# Subtest: builds activity view with agent filter
|
||||||
|
ok 4 - builds activity view with agent filter
|
||||||
|
---
|
||||||
|
duration_ms: 0.0913
|
||||||
|
...
|
||||||
|
# Subtest: preserves existing params when adding new ones
|
||||||
|
ok 5 - preserves existing params when adding new ones
|
||||||
|
---
|
||||||
|
duration_ms: 0.1507
|
||||||
|
...
|
||||||
|
# Subtest: removes params when set to null
|
||||||
|
ok 6 - removes params when set to null
|
||||||
|
---
|
||||||
|
duration_ms: 0.1207
|
||||||
|
...
|
||||||
|
# Subtest: returns root when all params cleared
|
||||||
|
ok 7 - returns root when all params cleared
|
||||||
|
---
|
||||||
|
duration_ms: 0.0871
|
||||||
|
...
|
||||||
|
1..7
|
||||||
|
ok 6 - URL Building - State to URL
|
||||||
|
---
|
||||||
|
duration_ms: 1.0559
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Complex URL Scenarios
|
||||||
|
# Subtest: handles all params together
|
||||||
|
ok 1 - handles all params together
|
||||||
|
---
|
||||||
|
duration_ms: 0.1183
|
||||||
|
...
|
||||||
|
# Subtest: empty string values treated as null/empty
|
||||||
|
ok 2 - empty string values treated as null/empty
|
||||||
|
---
|
||||||
|
duration_ms: 0.0817
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 7 - Complex URL Scenarios
|
||||||
|
---
|
||||||
|
duration_ms: 0.2733
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Deep Link Patterns - From Card Icons
|
||||||
|
# Subtest: SocialCard Graph icon: /?view=graph&task={id}
|
||||||
|
ok 1 - SocialCard Graph icon: /?view=graph&task={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.1285
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Graph icon: /?view=graph&swarm={id}
|
||||||
|
ok 2 - SwarmCard Graph icon: /?view=graph&swarm={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.1297
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Timeline icon: /?view=activity&swarm={id}
|
||||||
|
ok 3 - SwarmCard Timeline icon: /?view=activity&swarm={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.0782
|
||||||
|
...
|
||||||
|
# Subtest: Agent avatar click: /?view=activity&agent={id}
|
||||||
|
ok 4 - Agent avatar click: /?view=activity&agent={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.0769
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 8 - Deep Link Patterns - From Card Icons
|
||||||
|
---
|
||||||
|
duration_ms: 0.5198
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..8
|
||||||
|
ok 1 - URL State Integration - bb-ui2.22
|
||||||
|
---
|
||||||
|
duration_ms: 7.8584
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
# tests 31
|
||||||
|
# suites 9
|
||||||
|
# pass 31
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 435.1391
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: useGraphAnalysis - module exports
|
||||||
|
ok 1 - useGraphAnalysis - module exports
|
||||||
|
---
|
||||||
|
duration_ms: 29.8133
|
||||||
|
...
|
||||||
|
# Subtest: useGraphAnalysis underlying logic - graphModel is built correctly
|
||||||
|
ok 2 - useGraphAnalysis underlying logic - graphModel is built correctly
|
||||||
|
---
|
||||||
|
duration_ms: 10.1561
|
||||||
|
...
|
||||||
|
# Subtest: useGraphAnalysis underlying logic - cycleNodeIdSet detects cycles
|
||||||
|
ok 3 - useGraphAnalysis underlying logic - cycleNodeIdSet detects cycles
|
||||||
|
---
|
||||||
|
duration_ms: 0.5465
|
||||||
|
...
|
||||||
|
# Subtest: useGraphAnalysis underlying logic - cycleNodeIdSet empty for acyclic graph
|
||||||
|
ok 4 - useGraphAnalysis underlying logic - cycleNodeIdSet empty for acyclic graph
|
||||||
|
---
|
||||||
|
duration_ms: 0.1687
|
||||||
|
...
|
||||||
|
# Subtest: useGraphAnalysis underlying logic - blockerAnalysis returns blockers
|
||||||
|
ok 5 - useGraphAnalysis underlying logic - blockerAnalysis returns blockers
|
||||||
|
---
|
||||||
|
duration_ms: 0.4643
|
||||||
|
...
|
||||||
|
# Subtest: useGraphAnalysis underlying logic - blockerTooltipMap shows blocker info
|
||||||
|
ok 6 - useGraphAnalysis underlying logic - blockerTooltipMap shows blocker info
|
||||||
|
---
|
||||||
|
duration_ms: 0.3071
|
||||||
|
...
|
||||||
|
1..6
|
||||||
|
# tests 6
|
||||||
|
# suites 0
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 559.0462
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SmartDag - file exists and exports
|
||||||
|
ok 1 - SmartDag - file exists and exports
|
||||||
|
---
|
||||||
|
duration_ms: 4.3779
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - contains Filters toggle button
|
||||||
|
ok 2 - SmartDag - contains Filters toggle button
|
||||||
|
---
|
||||||
|
duration_ms: 0.949
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - contains Assign toggle button
|
||||||
|
ok 3 - SmartDag - contains Assign toggle button
|
||||||
|
---
|
||||||
|
duration_ms: 0.7066
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - contains WorkflowTabs
|
||||||
|
ok 4 - SmartDag - contains WorkflowTabs
|
||||||
|
---
|
||||||
|
duration_ms: 1.1698
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - supports onAssignModeChange callback
|
||||||
|
ok 5 - SmartDag - supports onAssignModeChange callback
|
||||||
|
---
|
||||||
|
duration_ms: 1.4824
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - supports onSelectedIssueChange callback
|
||||||
|
ok 6 - SmartDag - supports onSelectedIssueChange callback
|
||||||
|
---
|
||||||
|
duration_ms: 0.8891
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - imports TaskCardGrid
|
||||||
|
ok 7 - SmartDag - imports TaskCardGrid
|
||||||
|
---
|
||||||
|
duration_ms: 0.93
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - imports WorkflowGraph
|
||||||
|
ok 8 - SmartDag - imports WorkflowGraph
|
||||||
|
---
|
||||||
|
duration_ms: 0.8032
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - passes assignMode to WorkflowGraph
|
||||||
|
ok 9 - SmartDag - passes assignMode to WorkflowGraph
|
||||||
|
---
|
||||||
|
duration_ms: 0.9817
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - manages hideClosed filter
|
||||||
|
ok 10 - SmartDag - manages hideClosed filter
|
||||||
|
---
|
||||||
|
duration_ms: 1.1005
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - manages sortReadyFirst filter
|
||||||
|
ok 11 - SmartDag - manages sortReadyFirst filter
|
||||||
|
---
|
||||||
|
duration_ms: 0.7155
|
||||||
|
...
|
||||||
|
# Subtest: SmartDag - uses useGraphAnalysis hook
|
||||||
|
ok 12 - SmartDag - uses useGraphAnalysis hook
|
||||||
|
---
|
||||||
|
duration_ms: 0.6575
|
||||||
|
...
|
||||||
|
1..12
|
||||||
|
# tests 12
|
||||||
|
# suites 0
|
||||||
|
# pass 12
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 326.9817
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: UnifiedShell - file exists and exports
|
||||||
|
ok 1 - UnifiedShell - file exists and exports
|
||||||
|
---
|
||||||
|
duration_ms: 4.1613
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - has assignMode state
|
||||||
|
ok 2 - UnifiedShell - has assignMode state
|
||||||
|
---
|
||||||
|
duration_ms: 1.0953
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - has selectedAssignIssue state
|
||||||
|
ok 3 - UnifiedShell - has selectedAssignIssue state
|
||||||
|
---
|
||||||
|
duration_ms: 0.7628
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - passes onAssignModeChange to SmartDag
|
||||||
|
ok 4 - UnifiedShell - passes onAssignModeChange to SmartDag
|
||||||
|
---
|
||||||
|
duration_ms: 0.7559
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - passes onSelectedIssueChange to SmartDag
|
||||||
|
ok 5 - UnifiedShell - passes onSelectedIssueChange to SmartDag
|
||||||
|
---
|
||||||
|
duration_ms: 1.3606
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - imports AssignmentPanel
|
||||||
|
ok 6 - UnifiedShell - imports AssignmentPanel
|
||||||
|
---
|
||||||
|
duration_ms: 0.9392
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - checks bd health and renders setup warning
|
||||||
|
ok 7 - UnifiedShell - checks bd health and renders setup warning
|
||||||
|
---
|
||||||
|
duration_ms: 0.7768
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - renders AssignmentPanel conditionally
|
||||||
|
not ok 8 - UnifiedShell - renders AssignmentPanel conditionally
|
||||||
|
---
|
||||||
|
duration_ms: 1.9496
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\components\\unified-shell.test.tsx:2:3626'
|
||||||
|
failureType: 'testCodeFailure'
|
||||||
|
error: 'Should check view === graph && assignMode condition for AssignmentPanel'
|
||||||
|
code: 'ERR_ASSERTION'
|
||||||
|
name: 'AssertionError'
|
||||||
|
expected: true
|
||||||
|
actual: false
|
||||||
|
operator: '=='
|
||||||
|
stack: |-
|
||||||
|
TestContext.<anonymous> (C:\Users\Zenchant\codex\beadboard\tests\components\unified-shell.test.tsx:53:10)
|
||||||
|
async Test.run (node:internal/test_runner/test:935:9)
|
||||||
|
async Test.processPendingSubtests (node:internal/test_runner/test:633:7)
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - does not import SwarmWorkspace
|
||||||
|
ok 9 - UnifiedShell - does not import SwarmWorkspace
|
||||||
|
---
|
||||||
|
duration_ms: 1.647
|
||||||
|
...
|
||||||
|
# Subtest: UnifiedShell - does not import SwarmMissionPicker
|
||||||
|
ok 10 - UnifiedShell - does not import SwarmMissionPicker
|
||||||
|
---
|
||||||
|
duration_ms: 1.0699
|
||||||
|
...
|
||||||
|
1..10
|
||||||
|
# tests 10
|
||||||
|
# suites 0
|
||||||
|
# pass 9
|
||||||
|
# fail 1
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 309.2239
|
||||||
1395
.beads/npm_test_output3.txt
Normal file
1395
.beads/npm_test_output3.txt
Normal file
File diff suppressed because it is too large
Load diff
3
.beads/task1.txt
Normal file
3
.beads/task1.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
✓ Created issue: beadboard-txj.1 — Implement graph transitive reduction algorithm
|
||||||
|
Priority: P0
|
||||||
|
Status: open
|
||||||
3
.beads/task2.txt
Normal file
3
.beads/task2.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
✓ Created issue: beadboard-txj.2 — Apply Status Colors and Transitive Context to Graph Edges
|
||||||
|
Priority: P0
|
||||||
|
Status: open
|
||||||
3
.beads/task3.txt
Normal file
3
.beads/task3.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
✓ Created issue: beadboard-txj.3 — Implement Graph Focus (Selection) Interactivity
|
||||||
|
Priority: P1
|
||||||
|
Status: open
|
||||||
790
.beads/test_output.txt
Normal file
790
.beads/test_output.txt
Normal file
|
|
@ -0,0 +1,790 @@
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: bootstrap scaffold files exist
|
||||||
|
ok 1 - bootstrap scaffold files exist
|
||||||
|
---
|
||||||
|
duration_ms: 1.7879
|
||||||
|
...
|
||||||
|
# Subtest: package.json has next/react/typescript scripts and deps
|
||||||
|
ok 2 - package.json has next/react/typescript scripts and deps
|
||||||
|
---
|
||||||
|
duration_ms: 0.7269
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 2
|
||||||
|
# suites 0
|
||||||
|
# pass 2
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 109.641
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: BaseCard Component Contract
|
||||||
|
# Subtest: exports BaseCard component
|
||||||
|
ok 1 - exports BaseCard component
|
||||||
|
---
|
||||||
|
duration_ms: 49.7712
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 1 - BaseCard Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 50.4334
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: BaseCard Styling Logic
|
||||||
|
# Subtest: should be possible to import the component
|
||||||
|
ok 1 - should be possible to import the component
|
||||||
|
---
|
||||||
|
duration_ms: 1.6802
|
||||||
|
...
|
||||||
|
# Subtest: applies correct status border class for "ready" status
|
||||||
|
ok 2 - applies correct status border class for "ready" status
|
||||||
|
---
|
||||||
|
duration_ms: 14.5164
|
||||||
|
...
|
||||||
|
# Subtest: applies correct status border class for "blocked" status
|
||||||
|
ok 3 - applies correct status border class for "blocked" status
|
||||||
|
---
|
||||||
|
duration_ms: 5.9515
|
||||||
|
...
|
||||||
|
# Subtest: applies selection ring when selected prop is true
|
||||||
|
ok 4 - applies selection ring when selected prop is true
|
||||||
|
---
|
||||||
|
duration_ms: 2.9283
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 2 - BaseCard Styling Logic
|
||||||
|
---
|
||||||
|
duration_ms: 25.6463
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 5
|
||||||
|
# suites 2
|
||||||
|
# pass 5
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 387.1692
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: AgentAvatar Component Contract
|
||||||
|
# Subtest: exports AgentAvatar component
|
||||||
|
ok 1 - exports AgentAvatar component
|
||||||
|
---
|
||||||
|
duration_ms: 116.5064
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 1 - AgentAvatar Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 117.1492
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: AgentAvatar Role Styling
|
||||||
|
# Subtest: applies correct role color class for "ui" role
|
||||||
|
ok 1 - applies correct role color class for "ui" role
|
||||||
|
---
|
||||||
|
duration_ms: 7.4888
|
||||||
|
...
|
||||||
|
# Subtest: applies correct role color class for "orchestrator" role
|
||||||
|
ok 2 - applies correct role color class for "orchestrator" role
|
||||||
|
---
|
||||||
|
duration_ms: 1.8762
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - AgentAvatar Role Styling
|
||||||
|
---
|
||||||
|
duration_ms: 9.6138
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: AgentAvatar ZFC States
|
||||||
|
# Subtest: applies working pulse glow
|
||||||
|
ok 1 - applies working pulse glow
|
||||||
|
---
|
||||||
|
duration_ms: 1.3946
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - AgentAvatar ZFC States
|
||||||
|
---
|
||||||
|
duration_ms: 1.6685
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
# tests 4
|
||||||
|
# suites 3
|
||||||
|
# pass 4
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 435.7079
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SessionsHeader: Agent Grouping
|
||||||
|
# Subtest: groups agents by swarm
|
||||||
|
ok 1 - groups agents by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 2.0443
|
||||||
|
...
|
||||||
|
# Subtest: shows fallback bucket for unassigned agents
|
||||||
|
ok 2 - shows fallback bucket for unassigned agents
|
||||||
|
---
|
||||||
|
duration_ms: 0.1574
|
||||||
|
...
|
||||||
|
# Subtest: handles empty swarm groups
|
||||||
|
ok 3 - handles empty swarm groups
|
||||||
|
---
|
||||||
|
duration_ms: 0.1261
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 1 - SessionsHeader: Agent Grouping
|
||||||
|
---
|
||||||
|
duration_ms: 3.2814
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
# tests 3
|
||||||
|
# suites 1
|
||||||
|
# pass 3
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 267.9904
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: getAgentRoleColor returns correct color for known roles
|
||||||
|
ok 1 - getAgentRoleColor returns correct color for known roles
|
||||||
|
---
|
||||||
|
duration_ms: 0.7796
|
||||||
|
...
|
||||||
|
# Subtest: getAgentRoleColor returns default for unknown role
|
||||||
|
ok 2 - getAgentRoleColor returns default for unknown role
|
||||||
|
---
|
||||||
|
duration_ms: 0.1221
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 2
|
||||||
|
# suites 0
|
||||||
|
# pass 2
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 250.2131
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: parseIssuesJsonl applies defaults and preserves priority 0
|
||||||
|
ok 1 - parseIssuesJsonl applies defaults and preserves priority 0
|
||||||
|
---
|
||||||
|
duration_ms: 1.1101
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl skips malformed and blank lines
|
||||||
|
ok 2 - parseIssuesJsonl skips malformed and blank lines
|
||||||
|
---
|
||||||
|
duration_ms: 0.1893
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl filters tombstones by default
|
||||||
|
ok 3 - parseIssuesJsonl filters tombstones by default
|
||||||
|
---
|
||||||
|
duration_ms: 0.148
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl can include tombstones when requested
|
||||||
|
ok 4 - parseIssuesJsonl can include tombstones when requested
|
||||||
|
---
|
||||||
|
duration_ms: 0.1451
|
||||||
|
...
|
||||||
|
# Subtest: parseIssuesJsonl supports beads dependency schema with depends_on_id and parent-child
|
||||||
|
ok 5 - parseIssuesJsonl supports beads dependency schema with depends_on_id and parent-child
|
||||||
|
---
|
||||||
|
duration_ms: 0.7609
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
# tests 5
|
||||||
|
# suites 0
|
||||||
|
# pass 5
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 275.8118
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: canonicalizeWindowsPath normalizes separators and drive casing
|
||||||
|
ok 1 - canonicalizeWindowsPath normalizes separators and drive casing
|
||||||
|
---
|
||||||
|
duration_ms: 0.9571
|
||||||
|
...
|
||||||
|
# Subtest: windowsPathKey is case-insensitive stable key
|
||||||
|
ok 2 - windowsPathKey is case-insensitive stable key
|
||||||
|
---
|
||||||
|
duration_ms: 0.2276
|
||||||
|
...
|
||||||
|
# Subtest: toDisplayPath renders forward slashes for UI readability
|
||||||
|
ok 3 - toDisplayPath renders forward slashes for UI readability
|
||||||
|
---
|
||||||
|
duration_ms: 0.1456
|
||||||
|
...
|
||||||
|
# Subtest: sameWindowsPath handles case/separator differences
|
||||||
|
ok 4 - sameWindowsPath handles case/separator differences
|
||||||
|
---
|
||||||
|
duration_ms: 0.1285
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
# tests 4
|
||||||
|
# suites 0
|
||||||
|
# pass 4
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 257.9372
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: LeftPanel Component Contract
|
||||||
|
# Subtest: exports LeftPanel component
|
||||||
|
ok 1 - exports LeftPanel component
|
||||||
|
---
|
||||||
|
duration_ms: 476.382
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel accepts issues and onEpicSelect props
|
||||||
|
ok 2 - LeftPanel accepts issues and onEpicSelect props
|
||||||
|
---
|
||||||
|
duration_ms: 2.427
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - LeftPanel Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 480.2344
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Tree Structure
|
||||||
|
# Subtest: renders epics as expandable tree items
|
||||||
|
ok 1 - renders epics as expandable tree items
|
||||||
|
---
|
||||||
|
duration_ms: 2.4781
|
||||||
|
...
|
||||||
|
# Subtest: groups beads under their parent epic
|
||||||
|
ok 2 - groups beads under their parent epic
|
||||||
|
---
|
||||||
|
duration_ms: 2.3032
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - LeftPanel Tree Structure
|
||||||
|
---
|
||||||
|
duration_ms: 5.297
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Responsive Behavior
|
||||||
|
# Subtest: applies responsive classes for desktop, tablet, and mobile
|
||||||
|
ok 1 - applies responsive classes for desktop, tablet, and mobile
|
||||||
|
---
|
||||||
|
duration_ms: 2.5744
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - LeftPanel Responsive Behavior
|
||||||
|
---
|
||||||
|
duration_ms: 2.8537
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: LeftPanel Scope Controls
|
||||||
|
# Subtest: renders scope section
|
||||||
|
ok 1 - renders scope section
|
||||||
|
---
|
||||||
|
duration_ms: 2.4409
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 4 - LeftPanel Scope Controls
|
||||||
|
---
|
||||||
|
duration_ms: 3.207
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
# tests 6
|
||||||
|
# suites 4
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 762.0118
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: TopBar Component Contract
|
||||||
|
# Subtest: exports TopBar component
|
||||||
|
ok 1 - exports TopBar component
|
||||||
|
---
|
||||||
|
duration_ms: 928.6733
|
||||||
|
...
|
||||||
|
# Subtest: TopBar component can be imported without errors
|
||||||
|
ok 2 - TopBar component can be imported without errors
|
||||||
|
---
|
||||||
|
duration_ms: 1.5359
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - TopBar Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 931.4628
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar View Tabs
|
||||||
|
# Subtest: renders view tabs: Social, Graph
|
||||||
|
ok 1 - renders view tabs: Social, Graph
|
||||||
|
---
|
||||||
|
duration_ms: 2.2099
|
||||||
|
...
|
||||||
|
# Subtest: active tab has bold text and accent underline
|
||||||
|
ok 2 - active tab has bold text and accent underline
|
||||||
|
---
|
||||||
|
duration_ms: 1.9226
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - TopBar View Tabs
|
||||||
|
---
|
||||||
|
duration_ms: 4.5329
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar Filter and Controls
|
||||||
|
# Subtest: renders filter/search input placeholder
|
||||||
|
ok 1 - renders filter/search input placeholder
|
||||||
|
---
|
||||||
|
duration_ms: 1.6872
|
||||||
|
...
|
||||||
|
# Subtest: renders settings placeholder
|
||||||
|
ok 2 - renders settings placeholder
|
||||||
|
---
|
||||||
|
duration_ms: 1.1945
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 3 - TopBar Filter and Controls
|
||||||
|
---
|
||||||
|
duration_ms: 3.3144
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
# tests 6
|
||||||
|
# suites 3
|
||||||
|
# pass 6
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 1275.1308
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: Mobile Navigation - Hamburger Menu
|
||||||
|
# Subtest: exports MobileNav component
|
||||||
|
ok 1 - exports MobileNav component
|
||||||
|
---
|
||||||
|
duration_ms: 146.0955
|
||||||
|
...
|
||||||
|
# Subtest: renders tab buttons: Social, Graph
|
||||||
|
ok 2 - renders tab buttons: Social, Graph
|
||||||
|
---
|
||||||
|
duration_ms: 2.4626
|
||||||
|
...
|
||||||
|
# Subtest: highlights active tab with accent color
|
||||||
|
ok 3 - highlights active tab with accent color
|
||||||
|
---
|
||||||
|
duration_ms: 2.4243
|
||||||
|
...
|
||||||
|
# Subtest: uses setView from useUrlState on tab click
|
||||||
|
ok 4 - uses setView from useUrlState on tab click
|
||||||
|
---
|
||||||
|
duration_ms: 2.3011
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 1 - Mobile Navigation - Hamburger Menu
|
||||||
|
---
|
||||||
|
duration_ms: 155.4009
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: TopBar Hamburger Menu
|
||||||
|
# Subtest: shows hamburger button on mobile and tablet
|
||||||
|
ok 1 - shows hamburger button on mobile and tablet
|
||||||
|
---
|
||||||
|
duration_ms: 594.7571
|
||||||
|
...
|
||||||
|
# Subtest: hamburger button opens left panel drawer
|
||||||
|
ok 2 - hamburger button opens left panel drawer
|
||||||
|
---
|
||||||
|
duration_ms: 1.5433
|
||||||
|
...
|
||||||
|
# Subtest: hides hamburger on desktop
|
||||||
|
ok 3 - hides hamburger on desktop
|
||||||
|
---
|
||||||
|
duration_ms: 1.3406
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 2 - TopBar Hamburger Menu
|
||||||
|
---
|
||||||
|
duration_ms: 598.2042
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
# tests 7
|
||||||
|
# suites 2
|
||||||
|
# pass 7
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 1074.08
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: SwarmCard Component Contract
|
||||||
|
# Subtest: exports SwarmCard component
|
||||||
|
ok 1 - exports SwarmCard component
|
||||||
|
---
|
||||||
|
duration_ms: 301.3252
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard component can be imported without errors
|
||||||
|
ok 2 - SwarmCard component can be imported without errors
|
||||||
|
---
|
||||||
|
duration_ms: 1.6098
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 1 - SwarmCard Component Contract
|
||||||
|
---
|
||||||
|
duration_ms: 304.0952
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Agent Roster
|
||||||
|
# Subtest: renders agent avatars with liveness glow
|
||||||
|
ok 1 - renders agent avatars with liveness glow
|
||||||
|
---
|
||||||
|
duration_ms: 2.012
|
||||||
|
...
|
||||||
|
# Subtest: displays agent current task when available
|
||||||
|
ok 2 - displays agent current task when available
|
||||||
|
---
|
||||||
|
duration_ms: 1.5509
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 2 - SwarmCard Agent Roster
|
||||||
|
---
|
||||||
|
duration_ms: 3.8907
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Progress Bar
|
||||||
|
# Subtest: renders progress bar showing completion percentage
|
||||||
|
ok 1 - renders progress bar showing completion percentage
|
||||||
|
---
|
||||||
|
duration_ms: 1.4962
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 3 - SwarmCard Progress Bar
|
||||||
|
---
|
||||||
|
duration_ms: 1.7516
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Attention Items
|
||||||
|
# Subtest: renders attention items with warning styling
|
||||||
|
ok 1 - renders attention items with warning styling
|
||||||
|
---
|
||||||
|
duration_ms: 2.1433
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 4 - SwarmCard Attention Items
|
||||||
|
---
|
||||||
|
duration_ms: 2.818
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard View-Jump Icons
|
||||||
|
# Subtest: renders view-jump icons for navigation
|
||||||
|
ok 1 - renders view-jump icons for navigation
|
||||||
|
---
|
||||||
|
duration_ms: 2.1341
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
ok 5 - SwarmCard View-Jump Icons
|
||||||
|
---
|
||||||
|
duration_ms: 2.4614
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
# tests 7
|
||||||
|
# suites 5
|
||||||
|
# pass 7
|
||||||
|
# fail 0
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 612.9682
|
||||||
|
TAP version 13
|
||||||
|
# Subtest: URL State Integration - bb-ui2.22
|
||||||
|
# Subtest: Valid URL Patterns - Social View
|
||||||
|
# Subtest: /?view=social - defaults to social view
|
||||||
|
ok 1 - /?view=social - defaults to social view
|
||||||
|
---
|
||||||
|
duration_ms: 0.9839
|
||||||
|
...
|
||||||
|
# Subtest: /?view=social&task=bb-buff.1&panel=open - task selected, panel open
|
||||||
|
ok 2 - /?view=social&task=bb-buff.1&panel=open - task selected, panel open
|
||||||
|
---
|
||||||
|
duration_ms: 0.2403
|
||||||
|
...
|
||||||
|
# Subtest: /?view=social&task=bb-ui2.22 - task with dots in ID
|
||||||
|
ok 3 - /?view=social&task=bb-ui2.22 - task with dots in ID
|
||||||
|
---
|
||||||
|
duration_ms: 0.1425
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 1 - Valid URL Patterns - Social View
|
||||||
|
---
|
||||||
|
duration_ms: 2.0615
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Valid URL Patterns - Graph View
|
||||||
|
# Subtest: /?view=graph - graph view default
|
||||||
|
not ok 1 - /?view=graph - graph view default
|
||||||
|
---
|
||||||
|
duration_ms: 3.8342
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:2578'
|
||||||
|
failureType: 'testCodeFailure'
|
||||||
|
error: |-
|
||||||
|
Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'overview' !== 'flow'
|
||||||
|
|
||||||
|
code: 'ERR_ASSERTION'
|
||||||
|
name: 'AssertionError'
|
||||||
|
expected: 'flow'
|
||||||
|
actual: 'overview'
|
||||||
|
operator: 'strictEqual'
|
||||||
|
stack: |-
|
||||||
|
TestContext.<anonymous> (C:\Users\Zenchant\codex\beadboard\tests\hooks\url-state-integration.test.ts:53:14)
|
||||||
|
Test.runInAsyncScope (node:async_hooks:211:14)
|
||||||
|
Test.run (node:internal/test_runner/test:934:25)
|
||||||
|
Test.start (node:internal/test_runner/test:833:17)
|
||||||
|
node:internal/test_runner/test:1318:71
|
||||||
|
node:internal/per_context/primordials:483:82
|
||||||
|
new Promise (<anonymous>)
|
||||||
|
new SafePromise (node:internal/per_context/primordials:451:29)
|
||||||
|
node:internal/per_context/primordials:483:9
|
||||||
|
Array.map (<anonymous>)
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&task=bb-buff.1 - graph with task selected
|
||||||
|
ok 2 - /?view=graph&task=bb-buff.1 - graph with task selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.2386
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=flow - flow tab selected
|
||||||
|
ok 3 - /?view=graph&graphTab=flow - flow tab selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.1616
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=overview - overview tab selected
|
||||||
|
ok 4 - /?view=graph&graphTab=overview - overview tab selected
|
||||||
|
---
|
||||||
|
duration_ms: 0.1198
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&swarm=bb-buff - graph filtered by swarm
|
||||||
|
ok 5 - /?view=graph&swarm=bb-buff - graph filtered by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 0.2708
|
||||||
|
...
|
||||||
|
1..5
|
||||||
|
not ok 2 - Valid URL Patterns - Graph View
|
||||||
|
---
|
||||||
|
duration_ms: 5.0492
|
||||||
|
type: 'suite'
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:2515'
|
||||||
|
failureType: 'subtestsFailed'
|
||||||
|
error: '1 subtest failed'
|
||||||
|
code: 'ERR_TEST_FAILURE'
|
||||||
|
...
|
||||||
|
# Subtest: Deprecated Swarm View Fallback
|
||||||
|
# Subtest: /?view=swarm - falls back to social (swarm view deprecated)
|
||||||
|
ok 1 - /?view=swarm - falls back to social (swarm view deprecated)
|
||||||
|
---
|
||||||
|
duration_ms: 0.2049
|
||||||
|
...
|
||||||
|
# Subtest: /?view=swarm&swarm=bb-buff - falls back to social but preserves swarmId
|
||||||
|
ok 2 - /?view=swarm&swarm=bb-buff - falls back to social but preserves swarmId
|
||||||
|
---
|
||||||
|
duration_ms: 0.271
|
||||||
|
...
|
||||||
|
# Subtest: /?view=swarm&swarm=bb-buff&panel=open - falls back to social with panel open
|
||||||
|
ok 3 - /?view=swarm&swarm=bb-buff&panel=open - falls back to social with panel open
|
||||||
|
---
|
||||||
|
duration_ms: 0.1949
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 3 - Deprecated Swarm View Fallback
|
||||||
|
---
|
||||||
|
duration_ms: 0.8363
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Valid URL Patterns - Activity View
|
||||||
|
# Subtest: /?view=activity - activity view default
|
||||||
|
ok 1 - /?view=activity - activity view default
|
||||||
|
---
|
||||||
|
duration_ms: 0.1634
|
||||||
|
...
|
||||||
|
# Subtest: /?view=activity&agent=bb-silver-castle - filtered by agent
|
||||||
|
ok 2 - /?view=activity&agent=bb-silver-castle - filtered by agent
|
||||||
|
---
|
||||||
|
duration_ms: 0.0883
|
||||||
|
...
|
||||||
|
# Subtest: /?view=activity&swarm=bb-buff - filtered by swarm
|
||||||
|
ok 3 - /?view=activity&swarm=bb-buff - filtered by swarm
|
||||||
|
---
|
||||||
|
duration_ms: 0.0828
|
||||||
|
...
|
||||||
|
1..3
|
||||||
|
ok 4 - Valid URL Patterns - Activity View
|
||||||
|
---
|
||||||
|
duration_ms: 0.5275
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Invalid Param Handling
|
||||||
|
# Subtest: /?view=invalid - invalid view defaults to social
|
||||||
|
ok 1 - /?view=invalid - invalid view defaults to social
|
||||||
|
---
|
||||||
|
duration_ms: 0.167
|
||||||
|
...
|
||||||
|
# Subtest: /?view=graph&graphTab=invalid - invalid graphTab defaults to flow
|
||||||
|
not ok 2 - /?view=graph&graphTab=invalid - invalid graphTab defaults to flow
|
||||||
|
---
|
||||||
|
duration_ms: 0.3965
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:6479'
|
||||||
|
failureType: 'testCodeFailure'
|
||||||
|
error: |-
|
||||||
|
Expected values to be strictly equal:
|
||||||
|
|
||||||
|
'overview' !== 'flow'
|
||||||
|
|
||||||
|
code: 'ERR_ASSERTION'
|
||||||
|
name: 'AssertionError'
|
||||||
|
expected: 'flow'
|
||||||
|
actual: 'overview'
|
||||||
|
operator: 'strictEqual'
|
||||||
|
stack: |-
|
||||||
|
TestContext.<anonymous> (C:\Users\Zenchant\codex\beadboard\tests\hooks\url-state-integration.test.ts:138:14)
|
||||||
|
Test.runInAsyncScope (node:async_hooks:211:14)
|
||||||
|
Test.run (node:internal/test_runner/test:934:25)
|
||||||
|
Suite.processPendingSubtests (node:internal/test_runner/test:633:18)
|
||||||
|
Test.postRun (node:internal/test_runner/test:1045:19)
|
||||||
|
Test.run (node:internal/test_runner/test:973:12)
|
||||||
|
async Promise.all (index 0)
|
||||||
|
async Suite.run (node:internal/test_runner/test:1320:7)
|
||||||
|
async Suite.processPendingSubtests (node:internal/test_runner/test:633:7)
|
||||||
|
...
|
||||||
|
# Subtest: /?panel=invalid - invalid panel defaults to open
|
||||||
|
ok 3 - /?panel=invalid - invalid panel defaults to open
|
||||||
|
---
|
||||||
|
duration_ms: 0.1068
|
||||||
|
...
|
||||||
|
# Subtest: /?task=invalid-id - invalid task ID still parsed (no validation)
|
||||||
|
ok 4 - /?task=invalid-id - invalid task ID still parsed (no validation)
|
||||||
|
---
|
||||||
|
duration_ms: 0.0904
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
not ok 5 - Invalid Param Handling
|
||||||
|
---
|
||||||
|
duration_ms: 0.9102
|
||||||
|
type: 'suite'
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:6178'
|
||||||
|
failureType: 'subtestsFailed'
|
||||||
|
error: '1 subtest failed'
|
||||||
|
code: 'ERR_TEST_FAILURE'
|
||||||
|
...
|
||||||
|
# Subtest: URL Building - State to URL
|
||||||
|
# Subtest: builds social view URL
|
||||||
|
ok 1 - builds social view URL
|
||||||
|
---
|
||||||
|
duration_ms: 0.2367
|
||||||
|
...
|
||||||
|
# Subtest: builds graph view with task URL
|
||||||
|
ok 2 - builds graph view with task URL
|
||||||
|
---
|
||||||
|
duration_ms: 0.0983
|
||||||
|
...
|
||||||
|
# Subtest: builds swarm view with swarm param
|
||||||
|
ok 3 - builds swarm view with swarm param
|
||||||
|
---
|
||||||
|
duration_ms: 0.0888
|
||||||
|
...
|
||||||
|
# Subtest: builds activity view with agent filter
|
||||||
|
ok 4 - builds activity view with agent filter
|
||||||
|
---
|
||||||
|
duration_ms: 0.159
|
||||||
|
...
|
||||||
|
# Subtest: preserves existing params when adding new ones
|
||||||
|
ok 5 - preserves existing params when adding new ones
|
||||||
|
---
|
||||||
|
duration_ms: 0.1957
|
||||||
|
...
|
||||||
|
# Subtest: removes params when set to null
|
||||||
|
ok 6 - removes params when set to null
|
||||||
|
---
|
||||||
|
duration_ms: 0.1242
|
||||||
|
...
|
||||||
|
# Subtest: returns root when all params cleared
|
||||||
|
ok 7 - returns root when all params cleared
|
||||||
|
---
|
||||||
|
duration_ms: 0.0988
|
||||||
|
...
|
||||||
|
1..7
|
||||||
|
ok 6 - URL Building - State to URL
|
||||||
|
---
|
||||||
|
duration_ms: 1.1817
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Complex URL Scenarios
|
||||||
|
# Subtest: handles all params together
|
||||||
|
ok 1 - handles all params together
|
||||||
|
---
|
||||||
|
duration_ms: 0.1361
|
||||||
|
...
|
||||||
|
# Subtest: empty string values treated as null/empty
|
||||||
|
ok 2 - empty string values treated as null/empty
|
||||||
|
---
|
||||||
|
duration_ms: 0.0964
|
||||||
|
...
|
||||||
|
1..2
|
||||||
|
ok 7 - Complex URL Scenarios
|
||||||
|
---
|
||||||
|
duration_ms: 0.317
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
# Subtest: Deep Link Patterns - From Card Icons
|
||||||
|
# Subtest: SocialCard Graph icon: /?view=graph&task={id}
|
||||||
|
ok 1 - SocialCard Graph icon: /?view=graph&task={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.1474
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Graph icon: /?view=graph&swarm={id}
|
||||||
|
ok 2 - SwarmCard Graph icon: /?view=graph&swarm={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.086
|
||||||
|
...
|
||||||
|
# Subtest: SwarmCard Timeline icon: /?view=activity&swarm={id}
|
||||||
|
ok 3 - SwarmCard Timeline icon: /?view=activity&swarm={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.2482
|
||||||
|
...
|
||||||
|
# Subtest: Agent avatar click: /?view=activity&agent={id}
|
||||||
|
ok 4 - Agent avatar click: /?view=activity&agent={id}
|
||||||
|
---
|
||||||
|
duration_ms: 0.1616
|
||||||
|
...
|
||||||
|
1..4
|
||||||
|
ok 8 - Deep Link Patterns - From Card Icons
|
||||||
|
---
|
||||||
|
duration_ms: 0.8134
|
||||||
|
type: 'suite'
|
||||||
|
...
|
||||||
|
1..8
|
||||||
|
not ok 1 - URL State Integration - bb-ui2.22
|
||||||
|
---
|
||||||
|
duration_ms: 12.6296
|
||||||
|
type: 'suite'
|
||||||
|
location: 'C:\\Users\\Zenchant\\codex\\beadboard\\tests\\hooks\\url-state-integration.test.ts:2:1269'
|
||||||
|
failureType: 'subtestsFailed'
|
||||||
|
error: '2 subtests failed'
|
||||||
|
code: 'ERR_TEST_FAILURE'
|
||||||
|
...
|
||||||
|
1..1
|
||||||
|
# tests 31
|
||||||
|
# suites 9
|
||||||
|
# pass 29
|
||||||
|
# fail 2
|
||||||
|
# cancelled 0
|
||||||
|
# skipped 0
|
||||||
|
# todo 0
|
||||||
|
# duration_ms 481.6532
|
||||||
1
.claude/skills/shadcn-ui
Symbolic link
1
.claude/skills/shadcn-ui
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,306 +0,0 @@
|
||||||
### shadcn/ui Chart Component - Installation
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add chart
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Basic Usage
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", desktop: 186, mobile: 80 },
|
|
||||||
{ month: "February", desktop: 305, mobile: 200 },
|
|
||||||
{ month: "March", desktop: 237, mobile: 120 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function BarChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
||||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - ChartConfig with Custom Colors
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
You can define custom colors directly in the configuration using hex values or CSS variables.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "#2563eb",
|
|
||||||
theme: {
|
|
||||||
light: "#2563eb",
|
|
||||||
dark: "#60a5fa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - CSS Variables
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Add chart color variables to your globals.css for consistent theming.
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Chart colors */
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.546 0.198 38.228);
|
|
||||||
--chart-4: oklch(0.596 0.151 343.253);
|
|
||||||
--chart-5: oklch(0.546 0.158 49.157);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.698 0.141 24.311);
|
|
||||||
--chart-4: oklch(0.676 0.172 171.196);
|
|
||||||
--chart-5: oklch(0.578 0.192 302.85);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Line Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a line chart with shadcn/ui charts component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
price: {
|
|
||||||
label: "Price",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", price: 186 },
|
|
||||||
{ month: "February", price: 305 },
|
|
||||||
{ month: "March", price: 237 },
|
|
||||||
{ month: "April", price: 203 },
|
|
||||||
{ month: "May", price: 276 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function LineChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
|
|
||||||
<Line
|
|
||||||
dataKey="price"
|
|
||||||
stroke="var(--color-price)"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Area Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating an area chart with gradient fill and legend.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Area, AreaChart, XAxis, YAxis } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
||||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
export function AreaChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<AreaChart data={chartData}>
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} />
|
|
||||||
<Area
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
stroke="var(--color-desktop)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
stroke="var(--color-mobile)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Pie Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a pie/donut chart with shadcn/ui.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Pie, PieChart } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
||||||
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
||||||
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const pieData = [
|
|
||||||
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
|
|
||||||
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
|
|
||||||
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PieChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<PieChart>
|
|
||||||
<Pie
|
|
||||||
data={pieData}
|
|
||||||
dataKey="visitors"
|
|
||||||
nameKey="browser"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
outerRadius={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</PieChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui ChartTooltipContent Props
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
|
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
|
||||||
|------|------|---------|-------------|
|
|
||||||
| `labelKey` | string | "label" | Key for tooltip label |
|
|
||||||
| `nameKey` | string | "name" | Key for tooltip name |
|
|
||||||
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
|
|
||||||
| `hideLabel` | boolean | false | Hide label |
|
|
||||||
| `hideIndicator` | boolean | false | Hide indicator |
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Accessibility
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<BarChart accessibilityLayer data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
- Keyboard arrow key navigation
|
|
||||||
- ARIA labels for chart elements
|
|
||||||
- Screen reader announcements for data values
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Recharts Dependencies
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component requires the following Recharts dependencies to be installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add recharts
|
|
||||||
npm install recharts
|
|
||||||
yarn add recharts
|
|
||||||
```
|
|
||||||
|
|
||||||
Recharts provides the following chart types:
|
|
||||||
- Area, Bar, Line, Pie, Composed
|
|
||||||
- Radar, RadialBar, Scatter
|
|
||||||
- Funnel, Treemap
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# shadcn/ui Learning Guide
|
|
||||||
|
|
||||||
This guide helps you learn shadcn/ui from basics to advanced patterns.
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
### 1. Understanding the Philosophy
|
|
||||||
|
|
||||||
shadcn/ui is different from traditional component libraries:
|
|
||||||
|
|
||||||
- **Copy-paste components**: Components are copied into your project, not installed as packages
|
|
||||||
- **Full customization**: You own the code and can modify it freely
|
|
||||||
- **Built on Radix UI**: Provides accessibility primitives
|
|
||||||
- **Styled with Tailwind**: Uses utility classes for consistent styling
|
|
||||||
|
|
||||||
### 2. Core Concepts to Master
|
|
||||||
|
|
||||||
#### Class Variance Authority (CVA)
|
|
||||||
Most components use CVA for variant management:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"base-classes",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "variant-classes",
|
|
||||||
destructive: "destructive-classes",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "size-classes",
|
|
||||||
sm: "small-classes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cn Utility Function
|
|
||||||
The `cn` function combines classes and resolves conflicts:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation Checklist
|
|
||||||
|
|
||||||
- [ ] Initialize a new project (Next.js, Vite, or Remix)
|
|
||||||
- [ ] Install Tailwind CSS
|
|
||||||
- [ ] Run `npx shadcn@latest init`
|
|
||||||
- [ ] Configure CSS variables
|
|
||||||
- [ ] Install first component: `npx shadcn@latest add button`
|
|
||||||
|
|
||||||
### 4. Essential Components to Learn First
|
|
||||||
|
|
||||||
1. **Button** - Learn variants and sizes
|
|
||||||
2. **Input** - Form inputs with labels
|
|
||||||
3. **Card** - Container components
|
|
||||||
4. **Form** - Form handling with React Hook Form
|
|
||||||
5. **Dialog** - Modal windows
|
|
||||||
6. **Select** - Dropdown selections
|
|
||||||
7. **Toast** - Notifications
|
|
||||||
|
|
||||||
### 5. Common Patterns
|
|
||||||
|
|
||||||
#### Form Pattern
|
|
||||||
Every form follows this structure:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
1. Define Zod schema
|
|
||||||
2. Create form with useForm
|
|
||||||
3. Wrap with Form component
|
|
||||||
4. Add FormField for each input
|
|
||||||
5. Handle submission
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Component Customization Pattern
|
|
||||||
To customize a component:
|
|
||||||
|
|
||||||
1. Copy component to your project
|
|
||||||
2. Modify the variants
|
|
||||||
3. Add new props if needed
|
|
||||||
4. Update types
|
|
||||||
|
|
||||||
### 6. Best Practices
|
|
||||||
|
|
||||||
- Always use TypeScript
|
|
||||||
- Follow the existing component structure
|
|
||||||
- Use semantic HTML when possible
|
|
||||||
- Test with screen readers for accessibility
|
|
||||||
- Keep components small and focused
|
|
||||||
|
|
||||||
### 7. Advanced Topics
|
|
||||||
|
|
||||||
- Creating custom components from scratch
|
|
||||||
- Building complex forms with validation
|
|
||||||
- Implementing dark mode
|
|
||||||
- Optimizing for performance
|
|
||||||
- Testing components
|
|
||||||
|
|
||||||
## Practice Exercises
|
|
||||||
|
|
||||||
### Exercise 1: Basic Setup
|
|
||||||
1. Create a new Next.js project
|
|
||||||
2. Set up shadcn/ui
|
|
||||||
3. Install and customize a Button component
|
|
||||||
4. Add a new variant "gradient"
|
|
||||||
|
|
||||||
### Exercise 2: Form Building
|
|
||||||
1. Create a contact form with:
|
|
||||||
- Name input (required)
|
|
||||||
- Email input (email validation)
|
|
||||||
- Message textarea (min length)
|
|
||||||
- Submit button with loading state
|
|
||||||
|
|
||||||
### Exercise 3: Component Combination
|
|
||||||
1. Build a settings page using:
|
|
||||||
- Card for layout
|
|
||||||
- Sheet for mobile menu
|
|
||||||
- Select for dropdowns
|
|
||||||
- Switch for toggles
|
|
||||||
- Toast for notifications
|
|
||||||
|
|
||||||
### Exercise 4: Custom Component
|
|
||||||
1. Create a custom Badge component
|
|
||||||
2. Support variants: default, secondary, destructive, outline
|
|
||||||
3. Support sizes: sm, default, lg
|
|
||||||
4. Add icon support
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Official Documentation](https://ui.shadcn.com)
|
|
||||||
- [GitHub Repository](https://github.com/shadcn/ui)
|
|
||||||
- [Examples Gallery](https://ui.shadcn.com/examples)
|
|
||||||
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
|
|
||||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,586 +0,0 @@
|
||||||
# shadcn.io Component Library
|
|
||||||
|
|
||||||
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
|
|
||||||
|
|
||||||
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
|
|
||||||
|
|
||||||
## Core Components
|
|
||||||
|
|
||||||
### Terminal Component
|
|
||||||
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Terminal } from "@/components/ui/terminal"
|
|
||||||
|
|
||||||
export default function DemoTerminal() {
|
|
||||||
return (
|
|
||||||
npm install @repo/terminalInstalling dependencies...npm start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dock Component
|
|
||||||
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Dock, DockIcon } from "@/components/ui/dock"
|
|
||||||
import { Home, Settings, User, Mail } from "lucide-react"
|
|
||||||
|
|
||||||
export default function AppDock() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credit Card Component
|
|
||||||
Interactive 3D credit card component with flip animations for payment forms and card displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { CreditCard } from "@/components/ui/credit-card"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function PaymentForm() {
|
|
||||||
const [cardData, setCardData] = useState({
|
|
||||||
number: "4532 1234 5678 9010",
|
|
||||||
holder: "JOHN DOE",
|
|
||||||
expiry: "12/28",
|
|
||||||
cvv: "123"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
console.log("Card flipped:", flipped)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Zoom Component
|
|
||||||
Zoomable image component with smooth modal transitions for image galleries and product displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ImageZoom } from "@/components/ui/image-zoom"
|
|
||||||
|
|
||||||
export default function ProductGallery() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Code Component
|
|
||||||
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { QRCode } from "@/components/ui/qr-code"
|
|
||||||
|
|
||||||
export default function ShareDialog() {
|
|
||||||
const shareUrl = "https://shadcn.io"
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Scan to visit shadcn.io
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Picker Component
|
|
||||||
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ColorPicker } from "@/components/ui/color-picker"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function ThemeCustomizer() {
|
|
||||||
const [color, setColor] = useState("#3b82f6")
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Selected: {color}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chart Components
|
|
||||||
|
|
||||||
### Bar Chart Component
|
|
||||||
Clean bar chart component for data comparison and categorical analysis using Recharts.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { BarChart } from "@/components/ui/bar-chart"
|
|
||||||
|
|
||||||
export default function SalesChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", sales: 4000, revenue: 2400 },
|
|
||||||
{ month: "Feb", sales: 3000, revenue: 1398 },
|
|
||||||
{ month: "Mar", sales: 2000, revenue: 9800 },
|
|
||||||
{ month: "Apr", sales: 2780, revenue: 3908 },
|
|
||||||
{ month: "May", sales: 1890, revenue: 4800 },
|
|
||||||
{ month: "Jun", sales: 2390, revenue: 3800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`$${value.toLocaleString()}`}
|
|
||||||
yAxisWidth={60}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Chart Component
|
|
||||||
Smooth line chart for visualizing trends and time-series data with multiple data series support.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LineChart } from "@/components/ui/line-chart"
|
|
||||||
|
|
||||||
export default function MetricsChart() {
|
|
||||||
const data = [
|
|
||||||
{ date: "2024-01", users: 1200, sessions: 3400 },
|
|
||||||
{ date: "2024-02", users: 1800, sessions: 4200 },
|
|
||||||
{ date: "2024-03", users: 2400, sessions: 5800 },
|
|
||||||
{ date: "2024-04", users: 3100, sessions: 7200 },
|
|
||||||
{ date: "2024-05", users: 3800, sessions: 8900 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Component
|
|
||||||
Donut chart component for displaying proportional data and percentage distributions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { PieChart } from "@/components/ui/pie-chart"
|
|
||||||
|
|
||||||
export default function MarketShareChart() {
|
|
||||||
const data = [
|
|
||||||
{ name: "Product A", value: 400, fill: "#3b82f6" },
|
|
||||||
{ name: "Product B", value: 300, fill: "#10b981" },
|
|
||||||
{ name: "Product C", value: 300, fill: "#f59e0b" },
|
|
||||||
{ name: "Product D", value: 200, fill: "#ef4444" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${entry.name}: ${entry.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Area Chart Component
|
|
||||||
Stacked area chart for visualizing volume changes over time with multiple data series.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AreaChart } from "@/components/ui/area-chart"
|
|
||||||
|
|
||||||
export default function TrafficChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
|
|
||||||
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
|
|
||||||
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
|
|
||||||
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
|
|
||||||
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Radar Chart Component
|
|
||||||
Multi-axis chart for comparing multiple variables across different categories simultaneously.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RadarChart } from "@/components/ui/radar-chart"
|
|
||||||
|
|
||||||
export default function SkillsChart() {
|
|
||||||
const data = [
|
|
||||||
{ skill: "JavaScript", score: 85, industry: 75 },
|
|
||||||
{ skill: "TypeScript", score: 80, industry: 70 },
|
|
||||||
{ skill: "React", score: 90, industry: 80 },
|
|
||||||
{ skill: "Node.js", score: 75, industry: 72 },
|
|
||||||
{ skill: "CSS", score: 88, industry: 78 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixed Chart Component
|
|
||||||
Combined bar and line chart for displaying multiple data types with different visualization methods.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MixedChart } from "@/components/ui/mixed-chart"
|
|
||||||
|
|
||||||
export default function PerformanceChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", revenue: 4000, growth: 5.2 },
|
|
||||||
{ month: "Feb", revenue: 4200, growth: 5.0 },
|
|
||||||
{ month: "Mar", revenue: 4800, growth: 14.3 },
|
|
||||||
{ month: "Apr", revenue: 5200, growth: 8.3 },
|
|
||||||
{ month: "May", revenue: 5800, growth: 11.5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Components
|
|
||||||
|
|
||||||
### Magnetic Effect Component
|
|
||||||
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Magnetic } from "@/components/ui/magnetic"
|
|
||||||
|
|
||||||
export default function InteractiveButton() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
Hover me
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animated Cursor Component
|
|
||||||
Custom animated cursor with interactive effects and particle trails for immersive experiences.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AnimatedCursor } from "@/components/ui/animated-cursor"
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Apple Hello Effect Component
|
|
||||||
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AppleHello } from "@/components/ui/apple-hello"
|
|
||||||
|
|
||||||
export default function WelcomeScreen() {
|
|
||||||
const greetings = [
|
|
||||||
{ text: "Hello", lang: "en" },
|
|
||||||
{ text: "Bonjour", lang: "fr" },
|
|
||||||
{ text: "こんにちは", lang: "ja" },
|
|
||||||
{ text: "Hola", lang: "es" },
|
|
||||||
{ text: "你好", lang: "zh" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Liquid Button Component
|
|
||||||
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LiquidButton } from "@/components/ui/liquid-button"
|
|
||||||
|
|
||||||
export default function CTASection() {
|
|
||||||
return (
|
|
||||||
console.log("CTA clicked")}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Text Component
|
|
||||||
Text animation that creates a rolling effect with smooth character transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RollingText } from "@/components/ui/rolling-text"
|
|
||||||
|
|
||||||
export default function AnimatedHeading() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shimmering Text Component
|
|
||||||
Text with animated shimmer effect for attention-grabbing headings and highlights.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ShimmeringText } from "@/components/ui/shimmering-text"
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## React Hooks
|
|
||||||
|
|
||||||
### useBoolean Hook
|
|
||||||
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useBoolean } from "@/hooks/use-boolean"
|
|
||||||
|
|
||||||
export default function TogglePanel() {
|
|
||||||
const modal = useBoolean(false)
|
|
||||||
const loading = useBoolean(false)
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
loading.setTrue()
|
|
||||||
try {
|
|
||||||
await submitForm()
|
|
||||||
modal.setFalse()
|
|
||||||
} finally {
|
|
||||||
loading.setFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Toggle Modal
|
|
||||||
{modal.value && (
|
|
||||||
|
|
||||||
|
|
||||||
Status: {loading.value ? "Saving..." : "Ready"}
|
|
||||||
|
|
||||||
Submit
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCounter Hook
|
|
||||||
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCounter } from "@/hooks/use-counter"
|
|
||||||
|
|
||||||
export default function CartCounter() {
|
|
||||||
const quantity = useCounter(0, { min: 0, max: 99 })
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
-
|
|
||||||
{quantity.value}
|
|
||||||
+
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useLocalStorage Hook
|
|
||||||
Persist state in browser localStorage with automatic serialization and deserialization.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useLocalStorage } from "@/hooks/use-local-storage"
|
|
||||||
|
|
||||||
export default function UserPreferences() {
|
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light")
|
|
||||||
const [settings, setSettings] = useLocalStorage("settings", {
|
|
||||||
notifications: true,
|
|
||||||
emailUpdates: false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(e.target.value)}>
|
|
||||||
LightDark setSettings({
|
|
||||||
...settings,
|
|
||||||
notifications: e.target.checked
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
Enable Notifications
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useDebounceValue Hook
|
|
||||||
Debounce values to prevent excessive updates and API calls during rapid user input.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useDebounceValue } from "@/hooks/use-debounce-value"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
|
|
||||||
export default function SearchBox() {
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const debouncedSearch = useDebounceValue(search, 500)
|
|
||||||
const [results, setResults] = useState([])
|
|
||||||
const [apiCalls, setApiCalls] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
setApiCalls(prev => prev + 1)
|
|
||||||
fetch(`/api/search?q=${debouncedSearch}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setResults)
|
|
||||||
}
|
|
||||||
}, [debouncedSearch])
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setSearch(e.target.value)}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
API calls: {apiCalls}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useHover Hook
|
|
||||||
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useHover } from "@/hooks/use-hover"
|
|
||||||
import { useRef } from "react"
|
|
||||||
|
|
||||||
export default function ImagePreview() {
|
|
||||||
const hoverRef = useRef(null)
|
|
||||||
const isHovering = useHover(hoverRef, {
|
|
||||||
enterDelay: 200,
|
|
||||||
leaveDelay: 100
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
{isHovering && (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCountdown Hook
|
|
||||||
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCountdown } from "@/hooks/use-countdown"
|
|
||||||
|
|
||||||
export default function OTPTimer() {
|
|
||||||
const countdown = useCountdown({
|
|
||||||
initialSeconds: 60,
|
|
||||||
onComplete: () => alert("OTP expired! Request a new code.")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
{countdown.seconds}s
|
|
||||||
|
|
||||||
{!countdown.isRunning ? (
|
|
||||||
Start
|
|
||||||
) : (
|
|
||||||
Pause
|
|
||||||
)}
|
|
||||||
Reset
|
|
||||||
|
|
||||||
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation and Usage
|
|
||||||
|
|
||||||
### CLI Installation
|
|
||||||
Install components directly into your project using the shadcn CLI for instant integration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize shadcn in your project
|
|
||||||
npx shadcn@latest init
|
|
||||||
|
|
||||||
# Add individual components
|
|
||||||
npx shadcn@latest add terminal
|
|
||||||
npx shadcn@latest add dock
|
|
||||||
npx shadcn@latest add credit-card
|
|
||||||
|
|
||||||
# Add multiple components at once
|
|
||||||
npx shadcn@latest add bar-chart line-chart pie-chart
|
|
||||||
|
|
||||||
# Add hooks
|
|
||||||
npx shadcn@latest add use-boolean use-counter use-local-storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Configuration
|
|
||||||
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tailwind.config.ts
|
|
||||||
import type { Config } from "tailwindcss"
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./pages/**/*.{ts,tsx}",
|
|
||||||
"./components/**/*.{ts,tsx}",
|
|
||||||
"./app/**/*.{ts,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
|
|
||||||
|
|
||||||
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.
|
|
||||||
File diff suppressed because it is too large
Load diff
1
.cline/skills/shadcn-ui
Symbolic link
1
.cline/skills/shadcn-ui
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,306 +0,0 @@
|
||||||
### shadcn/ui Chart Component - Installation
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add chart
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Basic Usage
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", desktop: 186, mobile: 80 },
|
|
||||||
{ month: "February", desktop: 305, mobile: 200 },
|
|
||||||
{ month: "March", desktop: 237, mobile: 120 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function BarChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
||||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - ChartConfig with Custom Colors
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
You can define custom colors directly in the configuration using hex values or CSS variables.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "#2563eb",
|
|
||||||
theme: {
|
|
||||||
light: "#2563eb",
|
|
||||||
dark: "#60a5fa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - CSS Variables
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Add chart color variables to your globals.css for consistent theming.
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Chart colors */
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.546 0.198 38.228);
|
|
||||||
--chart-4: oklch(0.596 0.151 343.253);
|
|
||||||
--chart-5: oklch(0.546 0.158 49.157);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.698 0.141 24.311);
|
|
||||||
--chart-4: oklch(0.676 0.172 171.196);
|
|
||||||
--chart-5: oklch(0.578 0.192 302.85);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Line Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a line chart with shadcn/ui charts component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
price: {
|
|
||||||
label: "Price",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", price: 186 },
|
|
||||||
{ month: "February", price: 305 },
|
|
||||||
{ month: "March", price: 237 },
|
|
||||||
{ month: "April", price: 203 },
|
|
||||||
{ month: "May", price: 276 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function LineChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
|
|
||||||
<Line
|
|
||||||
dataKey="price"
|
|
||||||
stroke="var(--color-price)"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Area Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating an area chart with gradient fill and legend.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Area, AreaChart, XAxis, YAxis } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
||||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
export function AreaChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<AreaChart data={chartData}>
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} />
|
|
||||||
<Area
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
stroke="var(--color-desktop)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
stroke="var(--color-mobile)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Pie Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a pie/donut chart with shadcn/ui.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Pie, PieChart } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
||||||
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
||||||
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const pieData = [
|
|
||||||
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
|
|
||||||
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
|
|
||||||
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PieChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<PieChart>
|
|
||||||
<Pie
|
|
||||||
data={pieData}
|
|
||||||
dataKey="visitors"
|
|
||||||
nameKey="browser"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
outerRadius={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</PieChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui ChartTooltipContent Props
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
|
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
|
||||||
|------|------|---------|-------------|
|
|
||||||
| `labelKey` | string | "label" | Key for tooltip label |
|
|
||||||
| `nameKey` | string | "name" | Key for tooltip name |
|
|
||||||
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
|
|
||||||
| `hideLabel` | boolean | false | Hide label |
|
|
||||||
| `hideIndicator` | boolean | false | Hide indicator |
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Accessibility
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<BarChart accessibilityLayer data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
- Keyboard arrow key navigation
|
|
||||||
- ARIA labels for chart elements
|
|
||||||
- Screen reader announcements for data values
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Recharts Dependencies
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component requires the following Recharts dependencies to be installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add recharts
|
|
||||||
npm install recharts
|
|
||||||
yarn add recharts
|
|
||||||
```
|
|
||||||
|
|
||||||
Recharts provides the following chart types:
|
|
||||||
- Area, Bar, Line, Pie, Composed
|
|
||||||
- Radar, RadialBar, Scatter
|
|
||||||
- Funnel, Treemap
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# shadcn/ui Learning Guide
|
|
||||||
|
|
||||||
This guide helps you learn shadcn/ui from basics to advanced patterns.
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
### 1. Understanding the Philosophy
|
|
||||||
|
|
||||||
shadcn/ui is different from traditional component libraries:
|
|
||||||
|
|
||||||
- **Copy-paste components**: Components are copied into your project, not installed as packages
|
|
||||||
- **Full customization**: You own the code and can modify it freely
|
|
||||||
- **Built on Radix UI**: Provides accessibility primitives
|
|
||||||
- **Styled with Tailwind**: Uses utility classes for consistent styling
|
|
||||||
|
|
||||||
### 2. Core Concepts to Master
|
|
||||||
|
|
||||||
#### Class Variance Authority (CVA)
|
|
||||||
Most components use CVA for variant management:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"base-classes",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "variant-classes",
|
|
||||||
destructive: "destructive-classes",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "size-classes",
|
|
||||||
sm: "small-classes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cn Utility Function
|
|
||||||
The `cn` function combines classes and resolves conflicts:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation Checklist
|
|
||||||
|
|
||||||
- [ ] Initialize a new project (Next.js, Vite, or Remix)
|
|
||||||
- [ ] Install Tailwind CSS
|
|
||||||
- [ ] Run `npx shadcn@latest init`
|
|
||||||
- [ ] Configure CSS variables
|
|
||||||
- [ ] Install first component: `npx shadcn@latest add button`
|
|
||||||
|
|
||||||
### 4. Essential Components to Learn First
|
|
||||||
|
|
||||||
1. **Button** - Learn variants and sizes
|
|
||||||
2. **Input** - Form inputs with labels
|
|
||||||
3. **Card** - Container components
|
|
||||||
4. **Form** - Form handling with React Hook Form
|
|
||||||
5. **Dialog** - Modal windows
|
|
||||||
6. **Select** - Dropdown selections
|
|
||||||
7. **Toast** - Notifications
|
|
||||||
|
|
||||||
### 5. Common Patterns
|
|
||||||
|
|
||||||
#### Form Pattern
|
|
||||||
Every form follows this structure:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
1. Define Zod schema
|
|
||||||
2. Create form with useForm
|
|
||||||
3. Wrap with Form component
|
|
||||||
4. Add FormField for each input
|
|
||||||
5. Handle submission
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Component Customization Pattern
|
|
||||||
To customize a component:
|
|
||||||
|
|
||||||
1. Copy component to your project
|
|
||||||
2. Modify the variants
|
|
||||||
3. Add new props if needed
|
|
||||||
4. Update types
|
|
||||||
|
|
||||||
### 6. Best Practices
|
|
||||||
|
|
||||||
- Always use TypeScript
|
|
||||||
- Follow the existing component structure
|
|
||||||
- Use semantic HTML when possible
|
|
||||||
- Test with screen readers for accessibility
|
|
||||||
- Keep components small and focused
|
|
||||||
|
|
||||||
### 7. Advanced Topics
|
|
||||||
|
|
||||||
- Creating custom components from scratch
|
|
||||||
- Building complex forms with validation
|
|
||||||
- Implementing dark mode
|
|
||||||
- Optimizing for performance
|
|
||||||
- Testing components
|
|
||||||
|
|
||||||
## Practice Exercises
|
|
||||||
|
|
||||||
### Exercise 1: Basic Setup
|
|
||||||
1. Create a new Next.js project
|
|
||||||
2. Set up shadcn/ui
|
|
||||||
3. Install and customize a Button component
|
|
||||||
4. Add a new variant "gradient"
|
|
||||||
|
|
||||||
### Exercise 2: Form Building
|
|
||||||
1. Create a contact form with:
|
|
||||||
- Name input (required)
|
|
||||||
- Email input (email validation)
|
|
||||||
- Message textarea (min length)
|
|
||||||
- Submit button with loading state
|
|
||||||
|
|
||||||
### Exercise 3: Component Combination
|
|
||||||
1. Build a settings page using:
|
|
||||||
- Card for layout
|
|
||||||
- Sheet for mobile menu
|
|
||||||
- Select for dropdowns
|
|
||||||
- Switch for toggles
|
|
||||||
- Toast for notifications
|
|
||||||
|
|
||||||
### Exercise 4: Custom Component
|
|
||||||
1. Create a custom Badge component
|
|
||||||
2. Support variants: default, secondary, destructive, outline
|
|
||||||
3. Support sizes: sm, default, lg
|
|
||||||
4. Add icon support
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Official Documentation](https://ui.shadcn.com)
|
|
||||||
- [GitHub Repository](https://github.com/shadcn/ui)
|
|
||||||
- [Examples Gallery](https://ui.shadcn.com/examples)
|
|
||||||
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
|
|
||||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,586 +0,0 @@
|
||||||
# shadcn.io Component Library
|
|
||||||
|
|
||||||
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
|
|
||||||
|
|
||||||
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
|
|
||||||
|
|
||||||
## Core Components
|
|
||||||
|
|
||||||
### Terminal Component
|
|
||||||
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Terminal } from "@/components/ui/terminal"
|
|
||||||
|
|
||||||
export default function DemoTerminal() {
|
|
||||||
return (
|
|
||||||
npm install @repo/terminalInstalling dependencies...npm start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dock Component
|
|
||||||
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Dock, DockIcon } from "@/components/ui/dock"
|
|
||||||
import { Home, Settings, User, Mail } from "lucide-react"
|
|
||||||
|
|
||||||
export default function AppDock() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credit Card Component
|
|
||||||
Interactive 3D credit card component with flip animations for payment forms and card displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { CreditCard } from "@/components/ui/credit-card"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function PaymentForm() {
|
|
||||||
const [cardData, setCardData] = useState({
|
|
||||||
number: "4532 1234 5678 9010",
|
|
||||||
holder: "JOHN DOE",
|
|
||||||
expiry: "12/28",
|
|
||||||
cvv: "123"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
console.log("Card flipped:", flipped)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Zoom Component
|
|
||||||
Zoomable image component with smooth modal transitions for image galleries and product displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ImageZoom } from "@/components/ui/image-zoom"
|
|
||||||
|
|
||||||
export default function ProductGallery() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Code Component
|
|
||||||
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { QRCode } from "@/components/ui/qr-code"
|
|
||||||
|
|
||||||
export default function ShareDialog() {
|
|
||||||
const shareUrl = "https://shadcn.io"
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Scan to visit shadcn.io
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Picker Component
|
|
||||||
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ColorPicker } from "@/components/ui/color-picker"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function ThemeCustomizer() {
|
|
||||||
const [color, setColor] = useState("#3b82f6")
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Selected: {color}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chart Components
|
|
||||||
|
|
||||||
### Bar Chart Component
|
|
||||||
Clean bar chart component for data comparison and categorical analysis using Recharts.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { BarChart } from "@/components/ui/bar-chart"
|
|
||||||
|
|
||||||
export default function SalesChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", sales: 4000, revenue: 2400 },
|
|
||||||
{ month: "Feb", sales: 3000, revenue: 1398 },
|
|
||||||
{ month: "Mar", sales: 2000, revenue: 9800 },
|
|
||||||
{ month: "Apr", sales: 2780, revenue: 3908 },
|
|
||||||
{ month: "May", sales: 1890, revenue: 4800 },
|
|
||||||
{ month: "Jun", sales: 2390, revenue: 3800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`$${value.toLocaleString()}`}
|
|
||||||
yAxisWidth={60}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Chart Component
|
|
||||||
Smooth line chart for visualizing trends and time-series data with multiple data series support.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LineChart } from "@/components/ui/line-chart"
|
|
||||||
|
|
||||||
export default function MetricsChart() {
|
|
||||||
const data = [
|
|
||||||
{ date: "2024-01", users: 1200, sessions: 3400 },
|
|
||||||
{ date: "2024-02", users: 1800, sessions: 4200 },
|
|
||||||
{ date: "2024-03", users: 2400, sessions: 5800 },
|
|
||||||
{ date: "2024-04", users: 3100, sessions: 7200 },
|
|
||||||
{ date: "2024-05", users: 3800, sessions: 8900 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Component
|
|
||||||
Donut chart component for displaying proportional data and percentage distributions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { PieChart } from "@/components/ui/pie-chart"
|
|
||||||
|
|
||||||
export default function MarketShareChart() {
|
|
||||||
const data = [
|
|
||||||
{ name: "Product A", value: 400, fill: "#3b82f6" },
|
|
||||||
{ name: "Product B", value: 300, fill: "#10b981" },
|
|
||||||
{ name: "Product C", value: 300, fill: "#f59e0b" },
|
|
||||||
{ name: "Product D", value: 200, fill: "#ef4444" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${entry.name}: ${entry.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Area Chart Component
|
|
||||||
Stacked area chart for visualizing volume changes over time with multiple data series.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AreaChart } from "@/components/ui/area-chart"
|
|
||||||
|
|
||||||
export default function TrafficChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
|
|
||||||
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
|
|
||||||
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
|
|
||||||
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
|
|
||||||
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Radar Chart Component
|
|
||||||
Multi-axis chart for comparing multiple variables across different categories simultaneously.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RadarChart } from "@/components/ui/radar-chart"
|
|
||||||
|
|
||||||
export default function SkillsChart() {
|
|
||||||
const data = [
|
|
||||||
{ skill: "JavaScript", score: 85, industry: 75 },
|
|
||||||
{ skill: "TypeScript", score: 80, industry: 70 },
|
|
||||||
{ skill: "React", score: 90, industry: 80 },
|
|
||||||
{ skill: "Node.js", score: 75, industry: 72 },
|
|
||||||
{ skill: "CSS", score: 88, industry: 78 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixed Chart Component
|
|
||||||
Combined bar and line chart for displaying multiple data types with different visualization methods.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MixedChart } from "@/components/ui/mixed-chart"
|
|
||||||
|
|
||||||
export default function PerformanceChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", revenue: 4000, growth: 5.2 },
|
|
||||||
{ month: "Feb", revenue: 4200, growth: 5.0 },
|
|
||||||
{ month: "Mar", revenue: 4800, growth: 14.3 },
|
|
||||||
{ month: "Apr", revenue: 5200, growth: 8.3 },
|
|
||||||
{ month: "May", revenue: 5800, growth: 11.5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Components
|
|
||||||
|
|
||||||
### Magnetic Effect Component
|
|
||||||
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Magnetic } from "@/components/ui/magnetic"
|
|
||||||
|
|
||||||
export default function InteractiveButton() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
Hover me
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animated Cursor Component
|
|
||||||
Custom animated cursor with interactive effects and particle trails for immersive experiences.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AnimatedCursor } from "@/components/ui/animated-cursor"
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Apple Hello Effect Component
|
|
||||||
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AppleHello } from "@/components/ui/apple-hello"
|
|
||||||
|
|
||||||
export default function WelcomeScreen() {
|
|
||||||
const greetings = [
|
|
||||||
{ text: "Hello", lang: "en" },
|
|
||||||
{ text: "Bonjour", lang: "fr" },
|
|
||||||
{ text: "こんにちは", lang: "ja" },
|
|
||||||
{ text: "Hola", lang: "es" },
|
|
||||||
{ text: "你好", lang: "zh" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Liquid Button Component
|
|
||||||
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LiquidButton } from "@/components/ui/liquid-button"
|
|
||||||
|
|
||||||
export default function CTASection() {
|
|
||||||
return (
|
|
||||||
console.log("CTA clicked")}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Text Component
|
|
||||||
Text animation that creates a rolling effect with smooth character transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RollingText } from "@/components/ui/rolling-text"
|
|
||||||
|
|
||||||
export default function AnimatedHeading() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shimmering Text Component
|
|
||||||
Text with animated shimmer effect for attention-grabbing headings and highlights.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ShimmeringText } from "@/components/ui/shimmering-text"
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## React Hooks
|
|
||||||
|
|
||||||
### useBoolean Hook
|
|
||||||
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useBoolean } from "@/hooks/use-boolean"
|
|
||||||
|
|
||||||
export default function TogglePanel() {
|
|
||||||
const modal = useBoolean(false)
|
|
||||||
const loading = useBoolean(false)
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
loading.setTrue()
|
|
||||||
try {
|
|
||||||
await submitForm()
|
|
||||||
modal.setFalse()
|
|
||||||
} finally {
|
|
||||||
loading.setFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Toggle Modal
|
|
||||||
{modal.value && (
|
|
||||||
|
|
||||||
|
|
||||||
Status: {loading.value ? "Saving..." : "Ready"}
|
|
||||||
|
|
||||||
Submit
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCounter Hook
|
|
||||||
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCounter } from "@/hooks/use-counter"
|
|
||||||
|
|
||||||
export default function CartCounter() {
|
|
||||||
const quantity = useCounter(0, { min: 0, max: 99 })
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
-
|
|
||||||
{quantity.value}
|
|
||||||
+
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useLocalStorage Hook
|
|
||||||
Persist state in browser localStorage with automatic serialization and deserialization.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useLocalStorage } from "@/hooks/use-local-storage"
|
|
||||||
|
|
||||||
export default function UserPreferences() {
|
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light")
|
|
||||||
const [settings, setSettings] = useLocalStorage("settings", {
|
|
||||||
notifications: true,
|
|
||||||
emailUpdates: false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(e.target.value)}>
|
|
||||||
LightDark setSettings({
|
|
||||||
...settings,
|
|
||||||
notifications: e.target.checked
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
Enable Notifications
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useDebounceValue Hook
|
|
||||||
Debounce values to prevent excessive updates and API calls during rapid user input.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useDebounceValue } from "@/hooks/use-debounce-value"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
|
|
||||||
export default function SearchBox() {
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const debouncedSearch = useDebounceValue(search, 500)
|
|
||||||
const [results, setResults] = useState([])
|
|
||||||
const [apiCalls, setApiCalls] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
setApiCalls(prev => prev + 1)
|
|
||||||
fetch(`/api/search?q=${debouncedSearch}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setResults)
|
|
||||||
}
|
|
||||||
}, [debouncedSearch])
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setSearch(e.target.value)}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
API calls: {apiCalls}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useHover Hook
|
|
||||||
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useHover } from "@/hooks/use-hover"
|
|
||||||
import { useRef } from "react"
|
|
||||||
|
|
||||||
export default function ImagePreview() {
|
|
||||||
const hoverRef = useRef(null)
|
|
||||||
const isHovering = useHover(hoverRef, {
|
|
||||||
enterDelay: 200,
|
|
||||||
leaveDelay: 100
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
{isHovering && (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCountdown Hook
|
|
||||||
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCountdown } from "@/hooks/use-countdown"
|
|
||||||
|
|
||||||
export default function OTPTimer() {
|
|
||||||
const countdown = useCountdown({
|
|
||||||
initialSeconds: 60,
|
|
||||||
onComplete: () => alert("OTP expired! Request a new code.")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
{countdown.seconds}s
|
|
||||||
|
|
||||||
{!countdown.isRunning ? (
|
|
||||||
Start
|
|
||||||
) : (
|
|
||||||
Pause
|
|
||||||
)}
|
|
||||||
Reset
|
|
||||||
|
|
||||||
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation and Usage
|
|
||||||
|
|
||||||
### CLI Installation
|
|
||||||
Install components directly into your project using the shadcn CLI for instant integration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize shadcn in your project
|
|
||||||
npx shadcn@latest init
|
|
||||||
|
|
||||||
# Add individual components
|
|
||||||
npx shadcn@latest add terminal
|
|
||||||
npx shadcn@latest add dock
|
|
||||||
npx shadcn@latest add credit-card
|
|
||||||
|
|
||||||
# Add multiple components at once
|
|
||||||
npx shadcn@latest add bar-chart line-chart pie-chart
|
|
||||||
|
|
||||||
# Add hooks
|
|
||||||
npx shadcn@latest add use-boolean use-counter use-local-storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Configuration
|
|
||||||
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tailwind.config.ts
|
|
||||||
import type { Config } from "tailwindcss"
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./pages/**/*.{ts,tsx}",
|
|
||||||
"./components/**/*.{ts,tsx}",
|
|
||||||
"./app/**/*.{ts,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
|
|
||||||
|
|
||||||
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1 +1 @@
|
||||||
{}
|
{"user.email":"jordanlive121@gmail.com","user.name":"zenchant"}
|
||||||
1
.openhands/skills/shadcn-ui
Symbolic link
1
.openhands/skills/shadcn-ui
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,306 +0,0 @@
|
||||||
### shadcn/ui Chart Component - Installation
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add chart
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Basic Usage
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", desktop: 186, mobile: 80 },
|
|
||||||
{ month: "February", desktop: 305, mobile: 200 },
|
|
||||||
{ month: "March", desktop: 237, mobile: 120 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function BarChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
||||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - ChartConfig with Custom Colors
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
You can define custom colors directly in the configuration using hex values or CSS variables.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "#2563eb",
|
|
||||||
theme: {
|
|
||||||
light: "#2563eb",
|
|
||||||
dark: "#60a5fa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - CSS Variables
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Add chart color variables to your globals.css for consistent theming.
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Chart colors */
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.546 0.198 38.228);
|
|
||||||
--chart-4: oklch(0.596 0.151 343.253);
|
|
||||||
--chart-5: oklch(0.546 0.158 49.157);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.698 0.141 24.311);
|
|
||||||
--chart-4: oklch(0.676 0.172 171.196);
|
|
||||||
--chart-5: oklch(0.578 0.192 302.85);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Line Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a line chart with shadcn/ui charts component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
price: {
|
|
||||||
label: "Price",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", price: 186 },
|
|
||||||
{ month: "February", price: 305 },
|
|
||||||
{ month: "March", price: 237 },
|
|
||||||
{ month: "April", price: 203 },
|
|
||||||
{ month: "May", price: 276 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function LineChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
|
|
||||||
<Line
|
|
||||||
dataKey="price"
|
|
||||||
stroke="var(--color-price)"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Area Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating an area chart with gradient fill and legend.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Area, AreaChart, XAxis, YAxis } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
||||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
export function AreaChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<AreaChart data={chartData}>
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} />
|
|
||||||
<Area
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
stroke="var(--color-desktop)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
stroke="var(--color-mobile)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Pie Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a pie/donut chart with shadcn/ui.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Pie, PieChart } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
||||||
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
||||||
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const pieData = [
|
|
||||||
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
|
|
||||||
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
|
|
||||||
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PieChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<PieChart>
|
|
||||||
<Pie
|
|
||||||
data={pieData}
|
|
||||||
dataKey="visitors"
|
|
||||||
nameKey="browser"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
outerRadius={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</PieChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui ChartTooltipContent Props
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
|
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
|
||||||
|------|------|---------|-------------|
|
|
||||||
| `labelKey` | string | "label" | Key for tooltip label |
|
|
||||||
| `nameKey` | string | "name" | Key for tooltip name |
|
|
||||||
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
|
|
||||||
| `hideLabel` | boolean | false | Hide label |
|
|
||||||
| `hideIndicator` | boolean | false | Hide indicator |
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Accessibility
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<BarChart accessibilityLayer data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
- Keyboard arrow key navigation
|
|
||||||
- ARIA labels for chart elements
|
|
||||||
- Screen reader announcements for data values
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Recharts Dependencies
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component requires the following Recharts dependencies to be installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add recharts
|
|
||||||
npm install recharts
|
|
||||||
yarn add recharts
|
|
||||||
```
|
|
||||||
|
|
||||||
Recharts provides the following chart types:
|
|
||||||
- Area, Bar, Line, Pie, Composed
|
|
||||||
- Radar, RadialBar, Scatter
|
|
||||||
- Funnel, Treemap
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# shadcn/ui Learning Guide
|
|
||||||
|
|
||||||
This guide helps you learn shadcn/ui from basics to advanced patterns.
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
### 1. Understanding the Philosophy
|
|
||||||
|
|
||||||
shadcn/ui is different from traditional component libraries:
|
|
||||||
|
|
||||||
- **Copy-paste components**: Components are copied into your project, not installed as packages
|
|
||||||
- **Full customization**: You own the code and can modify it freely
|
|
||||||
- **Built on Radix UI**: Provides accessibility primitives
|
|
||||||
- **Styled with Tailwind**: Uses utility classes for consistent styling
|
|
||||||
|
|
||||||
### 2. Core Concepts to Master
|
|
||||||
|
|
||||||
#### Class Variance Authority (CVA)
|
|
||||||
Most components use CVA for variant management:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"base-classes",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "variant-classes",
|
|
||||||
destructive: "destructive-classes",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "size-classes",
|
|
||||||
sm: "small-classes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cn Utility Function
|
|
||||||
The `cn` function combines classes and resolves conflicts:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation Checklist
|
|
||||||
|
|
||||||
- [ ] Initialize a new project (Next.js, Vite, or Remix)
|
|
||||||
- [ ] Install Tailwind CSS
|
|
||||||
- [ ] Run `npx shadcn@latest init`
|
|
||||||
- [ ] Configure CSS variables
|
|
||||||
- [ ] Install first component: `npx shadcn@latest add button`
|
|
||||||
|
|
||||||
### 4. Essential Components to Learn First
|
|
||||||
|
|
||||||
1. **Button** - Learn variants and sizes
|
|
||||||
2. **Input** - Form inputs with labels
|
|
||||||
3. **Card** - Container components
|
|
||||||
4. **Form** - Form handling with React Hook Form
|
|
||||||
5. **Dialog** - Modal windows
|
|
||||||
6. **Select** - Dropdown selections
|
|
||||||
7. **Toast** - Notifications
|
|
||||||
|
|
||||||
### 5. Common Patterns
|
|
||||||
|
|
||||||
#### Form Pattern
|
|
||||||
Every form follows this structure:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
1. Define Zod schema
|
|
||||||
2. Create form with useForm
|
|
||||||
3. Wrap with Form component
|
|
||||||
4. Add FormField for each input
|
|
||||||
5. Handle submission
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Component Customization Pattern
|
|
||||||
To customize a component:
|
|
||||||
|
|
||||||
1. Copy component to your project
|
|
||||||
2. Modify the variants
|
|
||||||
3. Add new props if needed
|
|
||||||
4. Update types
|
|
||||||
|
|
||||||
### 6. Best Practices
|
|
||||||
|
|
||||||
- Always use TypeScript
|
|
||||||
- Follow the existing component structure
|
|
||||||
- Use semantic HTML when possible
|
|
||||||
- Test with screen readers for accessibility
|
|
||||||
- Keep components small and focused
|
|
||||||
|
|
||||||
### 7. Advanced Topics
|
|
||||||
|
|
||||||
- Creating custom components from scratch
|
|
||||||
- Building complex forms with validation
|
|
||||||
- Implementing dark mode
|
|
||||||
- Optimizing for performance
|
|
||||||
- Testing components
|
|
||||||
|
|
||||||
## Practice Exercises
|
|
||||||
|
|
||||||
### Exercise 1: Basic Setup
|
|
||||||
1. Create a new Next.js project
|
|
||||||
2. Set up shadcn/ui
|
|
||||||
3. Install and customize a Button component
|
|
||||||
4. Add a new variant "gradient"
|
|
||||||
|
|
||||||
### Exercise 2: Form Building
|
|
||||||
1. Create a contact form with:
|
|
||||||
- Name input (required)
|
|
||||||
- Email input (email validation)
|
|
||||||
- Message textarea (min length)
|
|
||||||
- Submit button with loading state
|
|
||||||
|
|
||||||
### Exercise 3: Component Combination
|
|
||||||
1. Build a settings page using:
|
|
||||||
- Card for layout
|
|
||||||
- Sheet for mobile menu
|
|
||||||
- Select for dropdowns
|
|
||||||
- Switch for toggles
|
|
||||||
- Toast for notifications
|
|
||||||
|
|
||||||
### Exercise 4: Custom Component
|
|
||||||
1. Create a custom Badge component
|
|
||||||
2. Support variants: default, secondary, destructive, outline
|
|
||||||
3. Support sizes: sm, default, lg
|
|
||||||
4. Add icon support
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Official Documentation](https://ui.shadcn.com)
|
|
||||||
- [GitHub Repository](https://github.com/shadcn/ui)
|
|
||||||
- [Examples Gallery](https://ui.shadcn.com/examples)
|
|
||||||
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
|
|
||||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,586 +0,0 @@
|
||||||
# shadcn.io Component Library
|
|
||||||
|
|
||||||
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
|
|
||||||
|
|
||||||
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
|
|
||||||
|
|
||||||
## Core Components
|
|
||||||
|
|
||||||
### Terminal Component
|
|
||||||
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Terminal } from "@/components/ui/terminal"
|
|
||||||
|
|
||||||
export default function DemoTerminal() {
|
|
||||||
return (
|
|
||||||
npm install @repo/terminalInstalling dependencies...npm start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dock Component
|
|
||||||
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Dock, DockIcon } from "@/components/ui/dock"
|
|
||||||
import { Home, Settings, User, Mail } from "lucide-react"
|
|
||||||
|
|
||||||
export default function AppDock() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credit Card Component
|
|
||||||
Interactive 3D credit card component with flip animations for payment forms and card displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { CreditCard } from "@/components/ui/credit-card"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function PaymentForm() {
|
|
||||||
const [cardData, setCardData] = useState({
|
|
||||||
number: "4532 1234 5678 9010",
|
|
||||||
holder: "JOHN DOE",
|
|
||||||
expiry: "12/28",
|
|
||||||
cvv: "123"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
console.log("Card flipped:", flipped)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Zoom Component
|
|
||||||
Zoomable image component with smooth modal transitions for image galleries and product displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ImageZoom } from "@/components/ui/image-zoom"
|
|
||||||
|
|
||||||
export default function ProductGallery() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Code Component
|
|
||||||
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { QRCode } from "@/components/ui/qr-code"
|
|
||||||
|
|
||||||
export default function ShareDialog() {
|
|
||||||
const shareUrl = "https://shadcn.io"
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Scan to visit shadcn.io
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Picker Component
|
|
||||||
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ColorPicker } from "@/components/ui/color-picker"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function ThemeCustomizer() {
|
|
||||||
const [color, setColor] = useState("#3b82f6")
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Selected: {color}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chart Components
|
|
||||||
|
|
||||||
### Bar Chart Component
|
|
||||||
Clean bar chart component for data comparison and categorical analysis using Recharts.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { BarChart } from "@/components/ui/bar-chart"
|
|
||||||
|
|
||||||
export default function SalesChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", sales: 4000, revenue: 2400 },
|
|
||||||
{ month: "Feb", sales: 3000, revenue: 1398 },
|
|
||||||
{ month: "Mar", sales: 2000, revenue: 9800 },
|
|
||||||
{ month: "Apr", sales: 2780, revenue: 3908 },
|
|
||||||
{ month: "May", sales: 1890, revenue: 4800 },
|
|
||||||
{ month: "Jun", sales: 2390, revenue: 3800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`$${value.toLocaleString()}`}
|
|
||||||
yAxisWidth={60}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Chart Component
|
|
||||||
Smooth line chart for visualizing trends and time-series data with multiple data series support.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LineChart } from "@/components/ui/line-chart"
|
|
||||||
|
|
||||||
export default function MetricsChart() {
|
|
||||||
const data = [
|
|
||||||
{ date: "2024-01", users: 1200, sessions: 3400 },
|
|
||||||
{ date: "2024-02", users: 1800, sessions: 4200 },
|
|
||||||
{ date: "2024-03", users: 2400, sessions: 5800 },
|
|
||||||
{ date: "2024-04", users: 3100, sessions: 7200 },
|
|
||||||
{ date: "2024-05", users: 3800, sessions: 8900 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Component
|
|
||||||
Donut chart component for displaying proportional data and percentage distributions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { PieChart } from "@/components/ui/pie-chart"
|
|
||||||
|
|
||||||
export default function MarketShareChart() {
|
|
||||||
const data = [
|
|
||||||
{ name: "Product A", value: 400, fill: "#3b82f6" },
|
|
||||||
{ name: "Product B", value: 300, fill: "#10b981" },
|
|
||||||
{ name: "Product C", value: 300, fill: "#f59e0b" },
|
|
||||||
{ name: "Product D", value: 200, fill: "#ef4444" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${entry.name}: ${entry.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Area Chart Component
|
|
||||||
Stacked area chart for visualizing volume changes over time with multiple data series.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AreaChart } from "@/components/ui/area-chart"
|
|
||||||
|
|
||||||
export default function TrafficChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
|
|
||||||
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
|
|
||||||
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
|
|
||||||
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
|
|
||||||
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Radar Chart Component
|
|
||||||
Multi-axis chart for comparing multiple variables across different categories simultaneously.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RadarChart } from "@/components/ui/radar-chart"
|
|
||||||
|
|
||||||
export default function SkillsChart() {
|
|
||||||
const data = [
|
|
||||||
{ skill: "JavaScript", score: 85, industry: 75 },
|
|
||||||
{ skill: "TypeScript", score: 80, industry: 70 },
|
|
||||||
{ skill: "React", score: 90, industry: 80 },
|
|
||||||
{ skill: "Node.js", score: 75, industry: 72 },
|
|
||||||
{ skill: "CSS", score: 88, industry: 78 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixed Chart Component
|
|
||||||
Combined bar and line chart for displaying multiple data types with different visualization methods.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MixedChart } from "@/components/ui/mixed-chart"
|
|
||||||
|
|
||||||
export default function PerformanceChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", revenue: 4000, growth: 5.2 },
|
|
||||||
{ month: "Feb", revenue: 4200, growth: 5.0 },
|
|
||||||
{ month: "Mar", revenue: 4800, growth: 14.3 },
|
|
||||||
{ month: "Apr", revenue: 5200, growth: 8.3 },
|
|
||||||
{ month: "May", revenue: 5800, growth: 11.5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Components
|
|
||||||
|
|
||||||
### Magnetic Effect Component
|
|
||||||
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Magnetic } from "@/components/ui/magnetic"
|
|
||||||
|
|
||||||
export default function InteractiveButton() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
Hover me
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animated Cursor Component
|
|
||||||
Custom animated cursor with interactive effects and particle trails for immersive experiences.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AnimatedCursor } from "@/components/ui/animated-cursor"
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Apple Hello Effect Component
|
|
||||||
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AppleHello } from "@/components/ui/apple-hello"
|
|
||||||
|
|
||||||
export default function WelcomeScreen() {
|
|
||||||
const greetings = [
|
|
||||||
{ text: "Hello", lang: "en" },
|
|
||||||
{ text: "Bonjour", lang: "fr" },
|
|
||||||
{ text: "こんにちは", lang: "ja" },
|
|
||||||
{ text: "Hola", lang: "es" },
|
|
||||||
{ text: "你好", lang: "zh" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Liquid Button Component
|
|
||||||
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LiquidButton } from "@/components/ui/liquid-button"
|
|
||||||
|
|
||||||
export default function CTASection() {
|
|
||||||
return (
|
|
||||||
console.log("CTA clicked")}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Text Component
|
|
||||||
Text animation that creates a rolling effect with smooth character transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RollingText } from "@/components/ui/rolling-text"
|
|
||||||
|
|
||||||
export default function AnimatedHeading() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shimmering Text Component
|
|
||||||
Text with animated shimmer effect for attention-grabbing headings and highlights.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ShimmeringText } from "@/components/ui/shimmering-text"
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## React Hooks
|
|
||||||
|
|
||||||
### useBoolean Hook
|
|
||||||
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useBoolean } from "@/hooks/use-boolean"
|
|
||||||
|
|
||||||
export default function TogglePanel() {
|
|
||||||
const modal = useBoolean(false)
|
|
||||||
const loading = useBoolean(false)
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
loading.setTrue()
|
|
||||||
try {
|
|
||||||
await submitForm()
|
|
||||||
modal.setFalse()
|
|
||||||
} finally {
|
|
||||||
loading.setFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Toggle Modal
|
|
||||||
{modal.value && (
|
|
||||||
|
|
||||||
|
|
||||||
Status: {loading.value ? "Saving..." : "Ready"}
|
|
||||||
|
|
||||||
Submit
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCounter Hook
|
|
||||||
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCounter } from "@/hooks/use-counter"
|
|
||||||
|
|
||||||
export default function CartCounter() {
|
|
||||||
const quantity = useCounter(0, { min: 0, max: 99 })
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
-
|
|
||||||
{quantity.value}
|
|
||||||
+
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useLocalStorage Hook
|
|
||||||
Persist state in browser localStorage with automatic serialization and deserialization.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useLocalStorage } from "@/hooks/use-local-storage"
|
|
||||||
|
|
||||||
export default function UserPreferences() {
|
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light")
|
|
||||||
const [settings, setSettings] = useLocalStorage("settings", {
|
|
||||||
notifications: true,
|
|
||||||
emailUpdates: false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(e.target.value)}>
|
|
||||||
LightDark setSettings({
|
|
||||||
...settings,
|
|
||||||
notifications: e.target.checked
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
Enable Notifications
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useDebounceValue Hook
|
|
||||||
Debounce values to prevent excessive updates and API calls during rapid user input.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useDebounceValue } from "@/hooks/use-debounce-value"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
|
|
||||||
export default function SearchBox() {
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const debouncedSearch = useDebounceValue(search, 500)
|
|
||||||
const [results, setResults] = useState([])
|
|
||||||
const [apiCalls, setApiCalls] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
setApiCalls(prev => prev + 1)
|
|
||||||
fetch(`/api/search?q=${debouncedSearch}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setResults)
|
|
||||||
}
|
|
||||||
}, [debouncedSearch])
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setSearch(e.target.value)}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
API calls: {apiCalls}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useHover Hook
|
|
||||||
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useHover } from "@/hooks/use-hover"
|
|
||||||
import { useRef } from "react"
|
|
||||||
|
|
||||||
export default function ImagePreview() {
|
|
||||||
const hoverRef = useRef(null)
|
|
||||||
const isHovering = useHover(hoverRef, {
|
|
||||||
enterDelay: 200,
|
|
||||||
leaveDelay: 100
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
{isHovering && (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCountdown Hook
|
|
||||||
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCountdown } from "@/hooks/use-countdown"
|
|
||||||
|
|
||||||
export default function OTPTimer() {
|
|
||||||
const countdown = useCountdown({
|
|
||||||
initialSeconds: 60,
|
|
||||||
onComplete: () => alert("OTP expired! Request a new code.")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
{countdown.seconds}s
|
|
||||||
|
|
||||||
{!countdown.isRunning ? (
|
|
||||||
Start
|
|
||||||
) : (
|
|
||||||
Pause
|
|
||||||
)}
|
|
||||||
Reset
|
|
||||||
|
|
||||||
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation and Usage
|
|
||||||
|
|
||||||
### CLI Installation
|
|
||||||
Install components directly into your project using the shadcn CLI for instant integration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize shadcn in your project
|
|
||||||
npx shadcn@latest init
|
|
||||||
|
|
||||||
# Add individual components
|
|
||||||
npx shadcn@latest add terminal
|
|
||||||
npx shadcn@latest add dock
|
|
||||||
npx shadcn@latest add credit-card
|
|
||||||
|
|
||||||
# Add multiple components at once
|
|
||||||
npx shadcn@latest add bar-chart line-chart pie-chart
|
|
||||||
|
|
||||||
# Add hooks
|
|
||||||
npx shadcn@latest add use-boolean use-counter use-local-storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Configuration
|
|
||||||
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tailwind.config.ts
|
|
||||||
import type { Config } from "tailwindcss"
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./pages/**/*.{ts,tsx}",
|
|
||||||
"./components/**/*.{ts,tsx}",
|
|
||||||
"./app/**/*.{ts,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
|
|
||||||
|
|
||||||
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.
|
|
||||||
File diff suppressed because it is too large
Load diff
533
docs/plans/2026-03-03-global-install-runtime-manager.md
Normal file
533
docs/plans/2026-03-03-global-install-runtime-manager.md
Normal file
|
|
@ -0,0 +1,533 @@
|
||||||
|
# BeadBoard Global Install Runtime Manager Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Replace repo-path shim install behavior with a robust global install model where `beadboard` runs from managed runtime home and works from any directory.
|
||||||
|
|
||||||
|
**Architecture:** Keep a hybrid install strategy with npm-global as primary and script bootstrap as fallback, but unify both paths behind one runtime manager contract (`~/.beadboard/runtime/<version>` + stable shims). Preserve backward compatibility by detecting legacy repo-bound shims and offering migration. Implement with strict TDD and explicit installer smoke tests.
|
||||||
|
|
||||||
|
**Tech Stack:** Node.js (ESM), TypeScript/tsx for CLI code, PowerShell + POSIX shell wrappers, GitHub Actions, Node test runner.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Implementation Checklist
|
||||||
|
|
||||||
|
### Task 0: Baseline and Safety Snapshot
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `beadboard/.beads/issues.jsonl` via `bd` commands only
|
||||||
|
- Read: `/mnt/c/Users/Zenchant/codex/beadboard/AGENTS.md`
|
||||||
|
- Read: `/mnt/c/Users/Zenchant/codex/beadboard/NEXT_SESSION_PROMPT.md`
|
||||||
|
|
||||||
|
**Step 1: Claim/track implementation bead(s)**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd /mnt/c/Users/Zenchant/codex/beadboard
|
||||||
|
bd create --title="Global installer runtime manager implementation" --description="Implement npm-global-first runtime manager with migration from repo-path shims" --type=task --priority=1 --label="installation,cli,runtime"
|
||||||
|
bd update <new-bead-id> --status in_progress --assignee <agent-bead-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Capture baseline gate status**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
npm run typecheck
|
||||||
|
npm run lint
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: existing known unrelated failures may appear; record exact failing files/tests in bead notes.
|
||||||
|
|
||||||
|
**Step 3: Commit checkpoint**
|
||||||
|
|
||||||
|
No code changes yet. Do not commit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Define Runtime Manager Contract
|
||||||
|
|
||||||
|
### Task 1: Add runtime manager ADR and package contract
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `docs/adr/2026-03-03-runtime-manager-global-install.md`
|
||||||
|
- Modify: `docs/adr/2026-03-03-global-installer-contract-and-manifest.md`
|
||||||
|
|
||||||
|
**Step 1: Write failing docs contract test**
|
||||||
|
|
||||||
|
Create:
|
||||||
|
- `tests/docs/runtime-manager-adr-contract.test.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
test('runtime manager ADR exists and declares runtime home strategy', async () => {
|
||||||
|
const raw = await fs.readFile(path.resolve('docs/adr/2026-03-03-runtime-manager-global-install.md'), 'utf8');
|
||||||
|
assert.match(raw, /~\/\.beadboard\/runtime/i);
|
||||||
|
assert.match(raw, /npm i -g beadboard/i);
|
||||||
|
assert.match(raw, /legacy repo-bound shim migration/i);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/docs/runtime-manager-adr-contract.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL (`ENOENT` for missing ADR file).
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
Add ADR with:
|
||||||
|
- runtime directory layout
|
||||||
|
- shim strategy
|
||||||
|
- update/uninstall model
|
||||||
|
- compatibility migration model
|
||||||
|
- failure modes and rollback
|
||||||
|
|
||||||
|
**Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/docs/runtime-manager-adr-contract.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tests/docs/runtime-manager-adr-contract.test.ts docs/adr/2026-03-03-runtime-manager-global-install.md docs/adr/2026-03-03-global-installer-contract-and-manifest.md
|
||||||
|
git commit -m "docs: define runtime manager global install contract"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Implement Runtime Manager Library
|
||||||
|
|
||||||
|
### Task 2: Add runtime manager core module with strict validation
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/lib/runtime-manager.ts`
|
||||||
|
- Create: `tests/lib/runtime-manager.test.ts`
|
||||||
|
|
||||||
|
**Step 1: Write failing test**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { getRuntimePaths, normalizeVersion } from '../../src/lib/runtime-manager';
|
||||||
|
|
||||||
|
test('normalizeVersion supports semver and rejects empty', () => {
|
||||||
|
assert.equal(normalizeVersion('1.2.3'), '1.2.3');
|
||||||
|
assert.throws(() => normalizeVersion(''));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getRuntimePaths builds ~/.beadboard/runtime/<version> layout', () => {
|
||||||
|
const p = getRuntimePaths('/tmp/home', '1.2.3');
|
||||||
|
assert.match(p.runtimeRoot, /runtime\/1\.2\.3$/);
|
||||||
|
assert.match(p.shimDir, /\.beadboard\/bin$/);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/lib/runtime-manager.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL (`Cannot find module '../../src/lib/runtime-manager'`).
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
Implement exports:
|
||||||
|
- `normalizeVersion(version: string): string`
|
||||||
|
- `getRuntimePaths(home: string, version: string)`
|
||||||
|
- `resolveInstallHome(env)`
|
||||||
|
|
||||||
|
Keep pure and side-effect free.
|
||||||
|
|
||||||
|
**Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/lib/runtime-manager.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/lib/runtime-manager.ts tests/lib/runtime-manager.test.ts
|
||||||
|
git commit -m "feat(installer): add runtime manager core library"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Move Launcher to Runtime-Aware Execution
|
||||||
|
|
||||||
|
### Task 3: Update launcher to run from managed runtime root
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `install/beadboard.mjs`
|
||||||
|
- Modify: `tests/scripts/beadboard-launcher.test.ts`
|
||||||
|
- Create: `tests/scripts/beadboard-launcher-runtime.test.ts`
|
||||||
|
|
||||||
|
**Step 1: Write failing test**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
const launcherPath = path.resolve('install/beadboard.mjs');
|
||||||
|
|
||||||
|
test('status --json reports runtime root and install mode', async () => {
|
||||||
|
const { stdout } = await execFileAsync(process.execPath, [launcherPath, 'status', '--json']);
|
||||||
|
const payload = JSON.parse(stdout);
|
||||||
|
assert.ok(payload.runtimeRoot);
|
||||||
|
assert.ok(payload.installMode);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL (missing fields).
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
In launcher:
|
||||||
|
- derive runtime home/version
|
||||||
|
- include `runtimeRoot`, `installMode`, `shimTarget` in JSON status
|
||||||
|
- preserve existing `start/open/status` behavior
|
||||||
|
|
||||||
|
**Step 4: Run tests to verify pass**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/scripts/beadboard-launcher.test.ts
|
||||||
|
node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add install/beadboard.mjs tests/scripts/beadboard-launcher.test.ts tests/scripts/beadboard-launcher-runtime.test.ts
|
||||||
|
git commit -m "feat(launcher): add runtime-aware status metadata"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Replace Repo-Bound Shim Targets
|
||||||
|
|
||||||
|
### Task 4: Make install wrappers point at runtime-managed target
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `install/install.sh`
|
||||||
|
- Modify: `install/install.ps1`
|
||||||
|
- Modify: `tests/scripts/install-wrappers-contract.test.ts`
|
||||||
|
- Modify: `tests/scripts/install-sh-smoke.test.ts`
|
||||||
|
- Create: `tests/scripts/install-legacy-migration.test.ts`
|
||||||
|
|
||||||
|
**Step 1: Write failing migration test**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
// simulate legacy shim text and verify installer rewrites to runtime target
|
||||||
|
test('installer migrates legacy repo-bound shim to runtime-managed shim', async () => {
|
||||||
|
assert.fail('implement');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/scripts/install-legacy-migration.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL.
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
In both wrappers:
|
||||||
|
- install runtime metadata file (`~/.beadboard/runtime/current.json`)
|
||||||
|
- rewrite `bb`/`beadboard` shims to resolve runtime target first
|
||||||
|
- detect old shim signatures and replace atomically
|
||||||
|
|
||||||
|
**Step 4: Run wrapper tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/scripts/install-wrappers-contract.test.ts
|
||||||
|
node --import tsx --test tests/scripts/install-sh-smoke.test.ts
|
||||||
|
node --import tsx --test tests/scripts/install-legacy-migration.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add install/install.sh install/install.ps1 tests/scripts/install-wrappers-contract.test.ts tests/scripts/install-sh-smoke.test.ts tests/scripts/install-legacy-migration.test.ts
|
||||||
|
git commit -m "feat(installer): migrate shims to runtime-managed targets"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Add npm-Global Entry and Self-Management Commands
|
||||||
|
|
||||||
|
### Task 5: Add CLI entrypoint commands (`self-update`, `uninstall`, `doctor`)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `package.json`
|
||||||
|
- Create: `bin/beadboard.js`
|
||||||
|
- Create: `src/cli/beadboard-cli.ts`
|
||||||
|
- Create: `tests/cli/beadboard-cli.test.ts`
|
||||||
|
|
||||||
|
**Step 1: Write failing CLI tests**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { runCli } from '../../src/cli/beadboard-cli';
|
||||||
|
|
||||||
|
test('doctor returns structured install diagnostics', async () => {
|
||||||
|
const out = await runCli(['doctor', '--json']);
|
||||||
|
assert.equal(out.ok, true);
|
||||||
|
assert.ok(out.installMode);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/cli/beadboard-cli.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL (missing module/command).
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
Add commands:
|
||||||
|
- `beadboard doctor --json`
|
||||||
|
- `beadboard self-update` (placeholder behavior with explicit output if publish not configured)
|
||||||
|
- `beadboard uninstall` (remove shims/runtime with `--yes` guard)
|
||||||
|
|
||||||
|
Add `bin` mapping in `package.json`.
|
||||||
|
|
||||||
|
**Step 4: Run test to verify pass**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/cli/beadboard-cli.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add package.json bin/beadboard.js src/cli/beadboard-cli.ts tests/cli/beadboard-cli.test.ts
|
||||||
|
git commit -m "feat(cli): add global entrypoint with doctor/update/uninstall commands"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Driver Alignment + Remediation UX Finalization
|
||||||
|
|
||||||
|
### Task 6: Update driver preflight/remediation copy for npm-global-first flow
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `skills/beadboard-driver/scripts/lib/driver-lib.mjs`
|
||||||
|
- Modify: `skills/beadboard-driver/scripts/session-preflight.mjs`
|
||||||
|
- Modify: `tests/skills/beadboard-driver/resolve-bb.test.ts`
|
||||||
|
- Modify: `tests/skills/beadboard-driver/session-preflight.test.ts`
|
||||||
|
|
||||||
|
**Step 1: Write failing test assertion**
|
||||||
|
|
||||||
|
Add expectation:
|
||||||
|
- remediation mentions `npm i -g beadboard` as primary
|
||||||
|
- install script remains fallback
|
||||||
|
|
||||||
|
**Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts
|
||||||
|
node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL (copy mismatch).
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
Update remediation strings to:
|
||||||
|
1. npm-global install
|
||||||
|
2. fallback installer wrapper
|
||||||
|
3. BB_REPO override guidance
|
||||||
|
|
||||||
|
**Step 4: Re-run tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts
|
||||||
|
node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add skills/beadboard-driver/scripts/lib/driver-lib.mjs skills/beadboard-driver/scripts/session-preflight.mjs tests/skills/beadboard-driver/resolve-bb.test.ts tests/skills/beadboard-driver/session-preflight.test.ts
|
||||||
|
git commit -m "feat(driver): prefer npm-global remediation with installer fallback"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: CI, Docs, and Release Readiness
|
||||||
|
|
||||||
|
### Task 7: Expand CI smoke and operator docs for global model
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `.github/workflows/installer-smoke.yml`
|
||||||
|
- Modify: `README.md`
|
||||||
|
- Modify: `docs/adr/2026-03-03-runtime-manager-global-install.md`
|
||||||
|
- Create: `docs/ops/global-install-rollout.md`
|
||||||
|
- Modify: `tests/scripts/installer-ci-contract.test.ts`
|
||||||
|
- Modify: `tests/docs/installer-quickstart-contract.test.ts`
|
||||||
|
|
||||||
|
**Step 1: Write failing test updates**
|
||||||
|
|
||||||
|
Add assertions for:
|
||||||
|
- npm-global command in docs
|
||||||
|
- runtime home path mentions
|
||||||
|
- CI step validating `beadboard doctor --json`
|
||||||
|
|
||||||
|
**Step 2: Run tests to verify fail**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/scripts/installer-ci-contract.test.ts
|
||||||
|
node --import tsx --test tests/docs/installer-quickstart-contract.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL.
|
||||||
|
|
||||||
|
**Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
Update docs and workflow:
|
||||||
|
- install paths
|
||||||
|
- migration notes
|
||||||
|
- recovery playbook
|
||||||
|
- supported platforms matrix
|
||||||
|
|
||||||
|
**Step 4: Re-run tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/scripts/installer-ci-contract.test.ts
|
||||||
|
node --import tsx --test tests/docs/installer-quickstart-contract.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .github/workflows/installer-smoke.yml README.md docs/adr/2026-03-03-runtime-manager-global-install.md docs/ops/global-install-rollout.md tests/scripts/installer-ci-contract.test.ts tests/docs/installer-quickstart-contract.test.ts
|
||||||
|
git commit -m "docs(ci): finalize global install runtime docs and smoke coverage"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Final Verification + Bead Closeout
|
||||||
|
|
||||||
|
### Task 8: Full-gate verification and close
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `beadboard` issue notes via `bd update`
|
||||||
|
- Modify: `NEXT_SESSION_PROMPT.md`
|
||||||
|
|
||||||
|
**Step 1: Run full gates**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
npm run typecheck
|
||||||
|
npm run lint
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS; if unrelated failures exist, capture exact files/tests.
|
||||||
|
|
||||||
|
**Step 2: Run targeted installer acceptance checks**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
node --import tsx --test tests/lib/runtime-manager.test.ts
|
||||||
|
node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts
|
||||||
|
node --import tsx --test tests/scripts/install-legacy-migration.test.ts
|
||||||
|
node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
**Step 3: Update beads with evidence**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
bd update <bead-id> --notes "<commands run + pass/fail details>"
|
||||||
|
bd close <bead-id> --reason "<completed outcome>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Update handoff**
|
||||||
|
|
||||||
|
Modify:
|
||||||
|
- `NEXT_SESSION_PROMPT.md` with shipped state + residual risks + next bead.
|
||||||
|
|
||||||
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add NEXT_SESSION_PROMPT.md
|
||||||
|
git commit -m "chore: close runtime-manager rollout with verification evidence"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References and Required Skills During Execution
|
||||||
|
|
||||||
|
1. `@test-driven-development`
|
||||||
|
2. `@verification-before-completion`
|
||||||
|
3. `@linus-beads-discipline`
|
||||||
|
4. `@beadboard-driver`
|
||||||
|
5. `@executing-plans` (required for implementation phase)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Plan complete and saved to `docs/plans/2026-03-03-global-install-runtime-manager.md`. Two execution options:
|
||||||
|
|
||||||
|
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
|
||||||
|
|
||||||
|
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
|
||||||
|
|
||||||
|
Which approach?
|
||||||
25
install/manifest.json
Normal file
25
install/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"version": "installer.v1",
|
||||||
|
"distribution": {
|
||||||
|
"packageName": "beadboard",
|
||||||
|
"shimNames": ["bb", "beadboard"]
|
||||||
|
},
|
||||||
|
"wrappers": {
|
||||||
|
"windows": {
|
||||||
|
"script": "install.ps1"
|
||||||
|
},
|
||||||
|
"posix": {
|
||||||
|
"script": "install.sh"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"start": "beadboard start",
|
||||||
|
"open": "beadboard open",
|
||||||
|
"status": "beadboard status"
|
||||||
|
},
|
||||||
|
"driver": {
|
||||||
|
"remediationMode": "detect_only",
|
||||||
|
"installSideEffects": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,78 +1,158 @@
|
||||||
---
|
---
|
||||||
name: beadboard-driver
|
name: beadboard-driver
|
||||||
description: Drive BeadBoard agent workflows with strict Operative Protocol v1 compliance. Use when handling bead lifecycle work that combines bd status commands with bb agent coordination (register/adopt, activity-lease, reserve/release, send/ack), especially in multi-agent sessions requiring silent observability and collision avoidance.
|
description: Complete operating manual for agents running work in external repos while humans orchestrate from BeadBoard.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Beadboard Driver (Operative Protocol v1)
|
# BeadBoard Driver
|
||||||
|
|
||||||
## Overview
|
BeadBoard is for teams that want autonomous agents without losing control of the work.
|
||||||
|
|
||||||
Use this skill to run repeatable `bd` + `bb` workflows under the **Activity Lease** (Parking Permit) model. Resolve `bb` safely, bootstrap via `bb-init`, coordinate via traceable incursions, and maintain liveness through real work.
|
Most agent setups break down the same way: work happens quickly, but visibility collapses, handoffs get fuzzy, and “done” starts meaning “probably done.” This skill fixes that operating problem.
|
||||||
|
|
||||||
## Core Workflow
|
With BeadBoard Driver, agents execute inside the target project repo, while humans orchestrate from BeadBoard as the control plane: assign, redirect, intervene, verify, and keep a durable coordination record.
|
||||||
|
|
||||||
|
BeadBoard project:
|
||||||
|
|
||||||
|
- GitHub: `https://github.com/zenchantlive/beadboard`
|
||||||
|
|
||||||
|
## What This Changes
|
||||||
|
|
||||||
|
- Work becomes observable, not performative.
|
||||||
|
- Ownership stays explicit at bead level.
|
||||||
|
- Handoffs and blockers become machine-readable events.
|
||||||
|
- Completion claims require evidence, not confidence.
|
||||||
|
- Multi-agent execution stays coordinated instead of chaotic.
|
||||||
|
|
||||||
|
## Operating Reality
|
||||||
|
|
||||||
|
- Agents usually run in a non-BeadBoard target repo.
|
||||||
|
- The user controls project scope from BeadBoard UI.
|
||||||
|
- Agents execute the current repo context they were assigned.
|
||||||
|
- `bd` remains source of truth for task/memory state.
|
||||||
|
|
||||||
|
## Start Here
|
||||||
|
|
||||||
|
Run this quick confidence check before you start a session:
|
||||||
|
|
||||||
1. **Bootstrap & Handshake**:
|
|
||||||
Run `bb-init` to resolve paths and identify yourself. Use `--adopt` if resuming a task with uncommitted changes.
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/bb-init.mjs --register <agent-name> --role <role> --json
|
bd --version
|
||||||
# OR
|
node skills/beadboard-driver/scripts/session-preflight.mjs
|
||||||
node scripts/bb-init.mjs --adopt <prior-agent-id> --non-interactive --json
|
node skills/beadboard-driver/scripts/resolve-bb.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Claim Territory**:
|
If discovery fails, install/repair from:
|
||||||
Reserve your work surface before making edits to prevent silent collisions.
|
|
||||||
|
- `https://github.com/zenchantlive/beadboard`
|
||||||
|
|
||||||
|
## Session Runbook
|
||||||
|
|
||||||
|
1. Diagnose environment.
|
||||||
|
2. Confirm preflight/discovery.
|
||||||
|
3. Establish session identity.
|
||||||
|
4. Read memory + ready work.
|
||||||
|
5. Claim bead with assignee.
|
||||||
|
6. Execute and coordinate via events.
|
||||||
|
7. Run verification gates.
|
||||||
|
8. Publish evidence and close.
|
||||||
|
9. Perform memory review.
|
||||||
|
|
||||||
|
## Core Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
& "$env:BB_REPO\bb.ps1" agent reserve --agent <agent-id> --scope "src/lib/*" --bead <bead-id>
|
# Diagnostics and discovery
|
||||||
bd update <bead-id> --status in_progress --claim
|
node skills/beadboard-driver/scripts/diagnose-env.mjs
|
||||||
|
node skills/beadboard-driver/scripts/session-preflight.mjs
|
||||||
|
node skills/beadboard-driver/scripts/resolve-bb.mjs
|
||||||
|
|
||||||
|
# Ensure project context exists in the target repository
|
||||||
|
node skills/beadboard-driver/scripts/ensure-project-context.mjs --project-root <repo>
|
||||||
|
|
||||||
|
# Identity helper
|
||||||
|
node skills/beadboard-driver/scripts/generate-agent-name.mjs
|
||||||
|
|
||||||
|
# Closeout evidence envelope
|
||||||
|
node skills/beadboard-driver/scripts/readiness-report.mjs --checks '<json>' --artifacts '<json>'
|
||||||
|
|
||||||
|
# Safe self-healing (dry-run default)
|
||||||
|
node skills/beadboard-driver/scripts/heal-common-issues.mjs --project-root <repo>
|
||||||
|
node skills/beadboard-driver/scripts/heal-common-issues.mjs --project-root <repo> --apply --fix-git-index-lock
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Physical Change -> Contextual Lookup**:
|
## Bead Lifecycle (Minimum Contract)
|
||||||
If you encounter uncommitted changes in a file you didn't personally edit: **STOP and Query**.
|
|
||||||
```bash
|
```bash
|
||||||
& "$env:BB_REPO\bb.ps1" agent status --agent <agent-id>
|
# Read context
|
||||||
& "$env:BB_REPO\bb.ps1" agent inbox --agent <agent-id> --state unread
|
bd show <memory-or-task-id>
|
||||||
|
bd ready
|
||||||
|
|
||||||
|
# Claim explicitly
|
||||||
|
bd update <bead-id> --status in_progress --assignee <agent-bead-id>
|
||||||
|
|
||||||
|
# Record evidence and close
|
||||||
|
bd update <bead-id> --notes "<commands + outputs>"
|
||||||
|
bd close <bead-id> --reason "<completed outcome>"
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Explain Deltas**:
|
## Use-The-Right-Doc Map
|
||||||
Send high-fidelity signals when you hit milestones or incursions.
|
|
||||||
|
### `references/memory-system.md`
|
||||||
|
Use when you need to query/apply/create canonical memory, validate provenance, or decide whether a lesson belongs in memory vs task notes.
|
||||||
|
|
||||||
|
### `references/coord-events-sessions-ack.md`
|
||||||
|
Use when you’re coordinating handoffs/blockers/incursions and need correct inbox/read/ack behavior.
|
||||||
|
|
||||||
|
### `references/session-lifecycle.md`
|
||||||
|
Use for end-to-end session choreography and closeout hygiene.
|
||||||
|
|
||||||
|
### `references/archetypes-templates-swarms.md`
|
||||||
|
Use when choosing team shape, role boundaries, and swarm ownership patterns.
|
||||||
|
|
||||||
|
### `references/missions-realtime.md`
|
||||||
|
Use when assigning work and troubleshooting stale/live-update behavior from mission/event flow.
|
||||||
|
|
||||||
|
### `references/command-matrix.md`
|
||||||
|
Use when you need exact command surfaces and argument shape.
|
||||||
|
|
||||||
|
### `references/failure-modes.md`
|
||||||
|
Use when preflight/discovery/coordination fails and you need deterministic recovery.
|
||||||
|
|
||||||
|
## Project Context Template
|
||||||
|
|
||||||
|
The skill ships a source template file: `project.template.md`.
|
||||||
|
|
||||||
|
Runtime contract:
|
||||||
|
|
||||||
|
- Agents should use `<target-repo>/project.md` for project context.
|
||||||
|
- If `<target-repo>/project.md` is missing, create it from `project.template.md`.
|
||||||
|
- If `<target-repo>/project.md` already exists, do not overwrite it.
|
||||||
|
|
||||||
|
Helper command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
& "$env:BB_REPO\bb.ps1" agent send --from <agent-id> --to <peer> --bead <bead-id> --category INFO --subject "Patched parser.ts for UI sync" --body "..."
|
node skills/beadboard-driver/scripts/ensure-project-context.mjs --project-root <repo>
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Liveness Maintenance**:
|
## Tests
|
||||||
Liveness is **Passive**. Any `bb agent` command extends your lease. Use `activity-lease` if you haven't run a command in > 10 minutes.
|
|
||||||
|
Skill-local contracts:
|
||||||
|
|
||||||
|
- `skills/beadboard-driver/tests/run-tests.mjs`
|
||||||
|
- `skills/beadboard-driver/tests/*.contract.test.mjs`
|
||||||
|
|
||||||
|
Repo-level coverage:
|
||||||
|
|
||||||
|
- `tests/skills/beadboard-driver/*.test.ts`
|
||||||
|
|
||||||
|
## Verification Gates
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
& "$env:BB_REPO\bb.ps1" agent activity-lease --agent <agent-id> --json
|
npm run typecheck
|
||||||
|
npm run lint
|
||||||
|
npm run test
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Closeout Evidence**:
|
If failures are outside your scope, cite exact failing files/tests and continue transparently.
|
||||||
```bash
|
|
||||||
node skills/beadboard-driver/scripts/readiness-report.mjs --checks '[{"name":"typecheck","ok":true}]' --artifacts '[{"path":"artifacts/final.png","required":true}]'
|
|
||||||
bd close <bead-id> --reason "..."
|
|
||||||
```
|
|
||||||
|
|
||||||
## Identity & Adoption Policy
|
## Bottom Line
|
||||||
|
|
||||||
- **Uniqueness**: Create one unique `adjective-noun` identity per session unless adopting.
|
This skill is the bridge between fast autonomous execution and human operator trust. Use it when speed matters, but coordination quality matters more.
|
||||||
- **Adoption Guardrails**: Adoption is ONLY allowed if uncommitted changes exist in the scope OR you own an `in_progress` bead.
|
|
||||||
- **Audit**: Every adoption triggers a `RESUME` event in the audit feed.
|
|
||||||
|
|
||||||
## Activity Lease (Parking Permit)
|
|
||||||
|
|
||||||
- **Active (0-15m)**: Lease is valid. You are protected from takeover.
|
|
||||||
- **Stale (15-30m)**: Lease expired. Others can takeover with `--takeover-stale`.
|
|
||||||
- **Evicted (30m+)**: Lease dead. Others should takeover and archive your reservation.
|
|
||||||
- **Idle (60m+)**: Ghost state. You are considered gone.
|
|
||||||
|
|
||||||
## Red Flags - STOP and Start Over
|
|
||||||
|
|
||||||
- **Silent Incursion**: Editing a reserved file without sending an `INFO` message.
|
|
||||||
- **Identity Reuse**: Reusing an agent ID from a previous session without an adoption handshake.
|
|
||||||
- **Mocking**: Implementing mocks instead of coordinating with the domain owner.
|
|
||||||
- **Terminal Pop-ups**: Spawning background workers that disrupt the user's desktop.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Command and argument contracts: `references/command-matrix.md`
|
|
||||||
- End-to-end session choreography: `references/session-lifecycle.md`
|
|
||||||
- Protocol Specification: `docs/protocols/operative-protocol-v1.md`
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
interface:
|
interface:
|
||||||
display_name: "Beadboard Driver"
|
display_name: "Beadboard Driver"
|
||||||
short_description: "Safe bd+bb agent workflow orchestration"
|
short_description: "BeadBoard control-plane workflow for agents in external repos"
|
||||||
default_prompt: "Use Beadboard Driver to resolve bb path, register a unique session agent, coordinate via bb agent commands, and produce verification-backed closeout notes."
|
default_prompt: "Use BeadBoard Driver v4 to run evidence-backed bd workflow in the target repo while the user orchestrates via BeadBoard UI; do not mutate project scope, use coordination events/inbox acks, and publish verification-backed closeout notes."
|
||||||
|
|
|
||||||
85
skills/beadboard-driver/project.template.md
Normal file
85
skills/beadboard-driver/project.template.md
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Project Driver Template
|
||||||
|
|
||||||
|
Use this file to define project-specific operating notes for agents using the BeadBoard Driver skill.
|
||||||
|
|
||||||
|
## Project Identity
|
||||||
|
|
||||||
|
- Project name:
|
||||||
|
- Repository root:
|
||||||
|
- Primary language/runtime:
|
||||||
|
- Primary package manager:
|
||||||
|
|
||||||
|
## BeadBoard Relationship
|
||||||
|
|
||||||
|
- BeadBoard host/UI location:
|
||||||
|
- Project registration identifier (if used):
|
||||||
|
- Notes about how this project appears in BeadBoard UI:
|
||||||
|
|
||||||
|
## Scope and Authority
|
||||||
|
|
||||||
|
- User controls project scope selection in BeadBoard UI.
|
||||||
|
- Agents do not change scope.
|
||||||
|
- Agent execution context in this repo:
|
||||||
|
|
||||||
|
## Command Baseline
|
||||||
|
|
||||||
|
- Install command:
|
||||||
|
- Build command:
|
||||||
|
- Typecheck command:
|
||||||
|
- Lint command:
|
||||||
|
- Test command:
|
||||||
|
- Smoke command (optional):
|
||||||
|
|
||||||
|
## Verification Policy Overrides
|
||||||
|
|
||||||
|
- Required gates for this project:
|
||||||
|
- Known slow gates and timeout guidance:
|
||||||
|
- Evidence format expected in bead notes:
|
||||||
|
|
||||||
|
## Environment Constraints
|
||||||
|
|
||||||
|
- OS/platform expectations:
|
||||||
|
- Required environment variables:
|
||||||
|
- Secrets handling guidance:
|
||||||
|
- Known path/shell quirks:
|
||||||
|
|
||||||
|
## Known Workarounds
|
||||||
|
|
||||||
|
Document only stable, repeatable workarounds.
|
||||||
|
|
||||||
|
1. Trigger:
|
||||||
|
- Symptom:
|
||||||
|
- Workaround:
|
||||||
|
- Verification:
|
||||||
|
- Owner:
|
||||||
|
|
||||||
|
2. Trigger:
|
||||||
|
- Symptom:
|
||||||
|
- Workaround:
|
||||||
|
- Verification:
|
||||||
|
- Owner:
|
||||||
|
|
||||||
|
## Coordination Defaults
|
||||||
|
|
||||||
|
- Default role/archetype mapping used by this project:
|
||||||
|
- Default handoff style:
|
||||||
|
- Blocker escalation policy:
|
||||||
|
- Ack expectations for blocker/handoff messages:
|
||||||
|
|
||||||
|
## Safety Guardrails
|
||||||
|
|
||||||
|
- Forbidden commands/actions for this repo:
|
||||||
|
- Files/paths requiring explicit reservation before edit:
|
||||||
|
- External systems that require human approval:
|
||||||
|
|
||||||
|
## Session Closeout Checklist
|
||||||
|
|
||||||
|
- [ ] Bead status/assignee updated
|
||||||
|
- [ ] Verification commands executed and recorded
|
||||||
|
- [ ] Artifacts attached/linked
|
||||||
|
- [ ] Memory review performed
|
||||||
|
- [ ] Follow-up beads created (if needed)
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
- YYYY-MM-DD: Initial project template completed.
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Archetypes, Templates, and Swarms
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define reusable team structure for multi-agent work so assignments are predictable, auditable, and easy for users to orchestrate from BeadBoard.
|
||||||
|
|
||||||
|
## Core Principle
|
||||||
|
|
||||||
|
Archetypes and templates define team composition. Missions define task execution.
|
||||||
|
|
||||||
|
Keep these concerns separate.
|
||||||
|
|
||||||
|
## Archetypes (Role Contracts)
|
||||||
|
|
||||||
|
An archetype is a role with clear responsibilities and deliverable expectations.
|
||||||
|
|
||||||
|
Baseline archetypes:
|
||||||
|
|
||||||
|
- `coder`: implements scoped changes and provides evidence.
|
||||||
|
- `reviewer`: validates quality, regressions, and acceptance criteria.
|
||||||
|
- `writer`: maintains user-facing docs, memory docs, and operator notes.
|
||||||
|
|
||||||
|
Optional archetypes may exist per project, but every archetype should specify:
|
||||||
|
|
||||||
|
- primary responsibilities,
|
||||||
|
- quality gates,
|
||||||
|
- handoff inputs/outputs,
|
||||||
|
- escalation triggers.
|
||||||
|
|
||||||
|
## Team Templates (Composition Contracts)
|
||||||
|
|
||||||
|
A template is a named role composition for repeatable work patterns.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- Fast lane: `coder + reviewer`
|
||||||
|
- Documentation lane: `writer + reviewer`
|
||||||
|
- Parallel lane: `orchestrator + coder + reviewer + writer`
|
||||||
|
|
||||||
|
Template quality rules:
|
||||||
|
|
||||||
|
- keep composition minimal,
|
||||||
|
- avoid duplicate authority,
|
||||||
|
- define ownership boundaries,
|
||||||
|
- define expected handoff order.
|
||||||
|
|
||||||
|
## Swarms (Runtime Team Instances)
|
||||||
|
|
||||||
|
A swarm is a live team instance operating on specific beads/epics.
|
||||||
|
|
||||||
|
Lifecycle:
|
||||||
|
|
||||||
|
1. Create swarm instance from a template or manual composition.
|
||||||
|
2. Join agents into explicit roles.
|
||||||
|
3. Assign beads with ownership.
|
||||||
|
4. Coordinate via events and inbox.
|
||||||
|
5. Leave or close swarm cleanly when complete.
|
||||||
|
|
||||||
|
## Command Surface (Representative)
|
||||||
|
|
||||||
|
Use your environment's swarm commands to manage lifecycle.
|
||||||
|
|
||||||
|
Expected operations:
|
||||||
|
|
||||||
|
- create swarm
|
||||||
|
- list/show swarm
|
||||||
|
- join swarm with role
|
||||||
|
- leave swarm
|
||||||
|
- close swarm
|
||||||
|
|
||||||
|
All swarm actions should produce observable state changes in BeadBoard views.
|
||||||
|
|
||||||
|
## Ownership Rules
|
||||||
|
|
||||||
|
- Every in-progress bead should have one clear assignee.
|
||||||
|
- Swarms may collaborate on an epic, but each bead needs an explicit owner.
|
||||||
|
- Multi-agent edits require reservation and coordination signals.
|
||||||
|
|
||||||
|
## User Orchestration Relationship
|
||||||
|
|
||||||
|
Users control orchestration from BeadBoard UI:
|
||||||
|
|
||||||
|
- choose team shape/template,
|
||||||
|
- assign or reassign roles,
|
||||||
|
- intervene on blockers,
|
||||||
|
- monitor throughput and liveness.
|
||||||
|
|
||||||
|
Agents execute according to assigned role and bead ownership.
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
- Role ambiguity (multiple agents assuming same responsibility).
|
||||||
|
- Oversized swarms with no clear ownership boundaries.
|
||||||
|
- Using templates as mission definitions.
|
||||||
|
- Running unassigned parallel work with no bead claim.
|
||||||
|
- Treating swarm closure as optional housekeeping.
|
||||||
|
|
@ -36,3 +36,6 @@
|
||||||
- `node skills/beadboard-driver/scripts/session-preflight.mjs`
|
- `node skills/beadboard-driver/scripts/session-preflight.mjs`
|
||||||
- `node skills/beadboard-driver/scripts/generate-agent-name.mjs`
|
- `node skills/beadboard-driver/scripts/generate-agent-name.mjs`
|
||||||
- `node skills/beadboard-driver/scripts/readiness-report.mjs --checks <json> --artifacts <json>`
|
- `node skills/beadboard-driver/scripts/readiness-report.mjs --checks <json> --artifacts <json>`
|
||||||
|
- `node skills/beadboard-driver/scripts/diagnose-env.mjs`
|
||||||
|
- `node skills/beadboard-driver/scripts/heal-common-issues.mjs [--project-root <path>] [--apply] [--fix-git-index-lock]`
|
||||||
|
- `node skills/beadboard-driver/scripts/ensure-project-context.mjs [--project-root <path>]`
|
||||||
|
|
|
||||||
121
skills/beadboard-driver/references/coord-events-sessions-ack.md
Normal file
121
skills/beadboard-driver/references/coord-events-sessions-ack.md
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Coordination Events, Sessions, and Acknowledgment
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define how agents communicate status, blockers, incursions, and handoffs in a machine-readable way that BeadBoard can render and users can act on.
|
||||||
|
|
||||||
|
## Operating Model
|
||||||
|
|
||||||
|
- Agent works in a target repository.
|
||||||
|
- User watches and orchestrates from BeadBoard UI.
|
||||||
|
- Agent communication must flow through coordination events and inbox state transitions, not ad-hoc notes.
|
||||||
|
|
||||||
|
## Event Categories
|
||||||
|
|
||||||
|
Use explicit categories with clear intent:
|
||||||
|
|
||||||
|
- `HANDOFF`: transfer ownership or next action.
|
||||||
|
- `BLOCKED`: explicit dependency or missing input.
|
||||||
|
- `RESUME`: adoption/resumption event.
|
||||||
|
- `INFO`: milestone or important context.
|
||||||
|
- `INCURSION`: overlap/collision signal for reserved scope.
|
||||||
|
|
||||||
|
## Session Stream Expectations
|
||||||
|
|
||||||
|
Session feeds should be audit-friendly:
|
||||||
|
|
||||||
|
- Every coordination event has sender, recipient/system target, bead id, and timestamp.
|
||||||
|
- `INCURSION` and `RESUME` are first-class timeline rows, not hidden diagnostics.
|
||||||
|
- Events should be understandable by humans without reading implementation code.
|
||||||
|
|
||||||
|
## Message Lifecycle
|
||||||
|
|
||||||
|
Inbox state machine:
|
||||||
|
|
||||||
|
1. `unread` when message is delivered.
|
||||||
|
2. `read` when recipient opens/reads message.
|
||||||
|
3. `acked` when recipient explicitly acknowledges.
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- Only recipient may ack.
|
||||||
|
- Acks are explicit, not implied by read.
|
||||||
|
- Blocker and handoff flows should request ack when coordination certainty is required.
|
||||||
|
|
||||||
|
## Recommended Command Patterns
|
||||||
|
|
||||||
|
Send structured coordination event:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bb agent send \
|
||||||
|
--from <agent-id> \
|
||||||
|
--to <peer-agent-id> \
|
||||||
|
--bead <bead-id> \
|
||||||
|
--category <HANDOFF|BLOCKED|RESUME|INFO|INCURSION> \
|
||||||
|
--subject "<short summary>" \
|
||||||
|
--body "<actionable details>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Read inbox for current bead/session work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bb agent inbox --agent <agent-id> --state unread --bead <bead-id>
|
||||||
|
bb agent read --agent <agent-id> --message <message-id>
|
||||||
|
bb agent ack --agent <agent-id> --message <message-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coordination Contracts
|
||||||
|
|
||||||
|
### Handoff
|
||||||
|
|
||||||
|
A `HANDOFF` should include:
|
||||||
|
|
||||||
|
- what is done,
|
||||||
|
- what remains,
|
||||||
|
- concrete next action,
|
||||||
|
- whether ack is required.
|
||||||
|
|
||||||
|
### Blocked
|
||||||
|
|
||||||
|
A `BLOCKED` should include:
|
||||||
|
|
||||||
|
- blocker description,
|
||||||
|
- requested action,
|
||||||
|
- urgency,
|
||||||
|
- ack requirement.
|
||||||
|
|
||||||
|
### Incursion
|
||||||
|
|
||||||
|
An `INCURSION` should include:
|
||||||
|
|
||||||
|
- overlap kind (`exact` or `partial`),
|
||||||
|
- owner identity,
|
||||||
|
- incoming identity,
|
||||||
|
- owner liveness,
|
||||||
|
- resolution hint.
|
||||||
|
|
||||||
|
### Resume
|
||||||
|
|
||||||
|
A `RESUME` should include:
|
||||||
|
|
||||||
|
- resume reason,
|
||||||
|
- prior session identity,
|
||||||
|
- adopted identity,
|
||||||
|
- evidence summary for safe adoption.
|
||||||
|
|
||||||
|
## UX Alignment
|
||||||
|
|
||||||
|
Session UI should map event semantics to plain-language actions:
|
||||||
|
|
||||||
|
- Handoff label: "Passed to"
|
||||||
|
- Blocked label: "Needs input"
|
||||||
|
- Read action: "Seen"
|
||||||
|
- Ack action: "Accepted"
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
- Using comments instead of coordination events for handoffs.
|
||||||
|
- Silent reservation collisions with no `INCURSION`/`INFO` signal.
|
||||||
|
- Treating read as ack.
|
||||||
|
- Sending vague events with no actionable payload.
|
||||||
|
- Closing a blocked bead without tracking unblock communication.
|
||||||
|
|
@ -38,3 +38,10 @@
|
||||||
- Do not write `.beads/issues.jsonl` directly.
|
- Do not write `.beads/issues.jsonl` directly.
|
||||||
- Do not close beads without verification evidence.
|
- Do not close beads without verification evidence.
|
||||||
- Do not bypass `BB_REPO` when it is set but invalid; fix it explicitly.
|
- Do not bypass `BB_REPO` when it is set but invalid; fix it explicitly.
|
||||||
|
|
||||||
|
## Local Environment Repair Signals
|
||||||
|
|
||||||
|
- `GIT_INDEX_LOCK_PRESENT`: stale git lock can block local operations.
|
||||||
|
- Recovery:
|
||||||
|
- confirm no active git process is using the repository,
|
||||||
|
- run `node skills/beadboard-driver/scripts/heal-common-issues.mjs --project-root <repo> --apply --fix-git-index-lock`.
|
||||||
|
|
|
||||||
110
skills/beadboard-driver/references/memory-system.md
Normal file
110
skills/beadboard-driver/references/memory-system.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Memory System
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Use BeadBoard memory to preserve reusable operating rules across sessions.
|
||||||
|
|
||||||
|
Memory is tracked in `bd` decision beads, not markdown notes. Task notes are for local execution context; canonical memory is for reusable rules.
|
||||||
|
|
||||||
|
## Execution Context
|
||||||
|
|
||||||
|
- Agents usually run in a target project repository, not the BeadBoard repository.
|
||||||
|
- Project scope is controlled by the user in the BeadBoard UI.
|
||||||
|
- Agents do not select or mutate project scope.
|
||||||
|
|
||||||
|
## Core Objects
|
||||||
|
|
||||||
|
- Anchor: domain parent bead (for example architecture, workflow, agent ops, reliability).
|
||||||
|
- Canonical memory: `type=decision` bead with memory labels.
|
||||||
|
- Provenance links: relations from memory to source evidence beads.
|
||||||
|
|
||||||
|
## Canonical Memory Contract
|
||||||
|
|
||||||
|
Create canonical memory only when the rule is reusable.
|
||||||
|
|
||||||
|
Required labels:
|
||||||
|
|
||||||
|
- `mem-canonical`
|
||||||
|
- `mem-hard` or `mem-soft`
|
||||||
|
- `memory`
|
||||||
|
- domain label such as `memory-agent`, `memory-arch`, `memory-workflow`, `memory-reliability`, `memory-ui`
|
||||||
|
|
||||||
|
Required description sections:
|
||||||
|
|
||||||
|
- `Scope:`
|
||||||
|
- `Out of Scope:`
|
||||||
|
- `Rule:`
|
||||||
|
- `Rationale:`
|
||||||
|
- `Failure Mode:`
|
||||||
|
|
||||||
|
Required acceptance style:
|
||||||
|
|
||||||
|
- Given/When/Then invariant
|
||||||
|
- Verification commands
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Query existing memory first.
|
||||||
|
2. Validate the memory provenance before relying on it.
|
||||||
|
3. Apply existing canonical memory to current task design.
|
||||||
|
4. If a new reusable rule appears, create canonical memory.
|
||||||
|
5. Link anchor, evidence, and related work with `bd dep relate`.
|
||||||
|
6. Ratify by closing the memory bead once complete.
|
||||||
|
7. For changes to an existing rule, supersede; do not rewrite history.
|
||||||
|
|
||||||
|
## Query and Validation Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd query "type=decision label:mem-canonical"
|
||||||
|
bd show <memory-id>
|
||||||
|
bd dep list <memory-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpretation checklist:
|
||||||
|
|
||||||
|
- Is the memory closed and canonical?
|
||||||
|
- Are provenance links present (2-5 evidence beads preferred)?
|
||||||
|
- Is the domain anchor relationship present?
|
||||||
|
|
||||||
|
## Create and Index Canonical Memory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd create --title="[MEMORY][<DOMAIN>][HARD|SOFT] <rule sentence>" \
|
||||||
|
--description="Scope: ...\nOut of Scope: ...\nRule: ...\nRationale: ...\nFailure Mode: ..." \
|
||||||
|
--type=decision --priority=1 \
|
||||||
|
--label="mem-canonical,mem-hard,memory,memory-<domain>"
|
||||||
|
|
||||||
|
bd dep relate <anchor-id> <memory-id>
|
||||||
|
bd dep relate <memory-id> <source-bead-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `mem-soft` when the rule is guidance and `mem-hard` when it is non-negotiable.
|
||||||
|
|
||||||
|
## Evolve Memory Safely
|
||||||
|
|
||||||
|
Use supersession when changing canonical rules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd supersede <old-memory-id> --with <new-memory-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not edit historical memory beads to represent new policy.
|
||||||
|
|
||||||
|
## Noise Budget
|
||||||
|
|
||||||
|
Apply memory sparingly per active task:
|
||||||
|
|
||||||
|
- 3-7 related memory nodes
|
||||||
|
- 0-2 blocker contracts
|
||||||
|
- 1 primary anchor domain per canonical memory
|
||||||
|
- 2-5 source-bead provenance links
|
||||||
|
|
||||||
|
If the lesson is not reusable, record it in task notes instead of creating memory.
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
- Writing policy in ad-hoc markdown only.
|
||||||
|
- Using blocker edges for memory indexing.
|
||||||
|
- Creating duplicate canonical memory for the same rule.
|
||||||
|
- Creating memory for one-off incidents without recurrence.
|
||||||
|
- Claiming memory-backed completion without verification evidence.
|
||||||
96
skills/beadboard-driver/references/missions-realtime.md
Normal file
96
skills/beadboard-driver/references/missions-realtime.md
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Missions and Realtime
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define how work assignments (missions) and realtime updates should behave so users can orchestrate external-repo execution from BeadBoard with confidence.
|
||||||
|
|
||||||
|
## Mission Model
|
||||||
|
|
||||||
|
A mission is an execution assignment bound to clear ownership and expected outputs.
|
||||||
|
|
||||||
|
Mission essentials:
|
||||||
|
|
||||||
|
- bead/epic scope,
|
||||||
|
- assigned owner,
|
||||||
|
- expected deliverable,
|
||||||
|
- dependency awareness,
|
||||||
|
- handoff path.
|
||||||
|
|
||||||
|
## Assignment Rules
|
||||||
|
|
||||||
|
- One active owner per bead-level mission.
|
||||||
|
- Multi-agent support is achieved through parallel missions, not shared ambiguous ownership.
|
||||||
|
- Mission assignment must be visible in BeadBoard and reflected in bead assignee/status fields.
|
||||||
|
|
||||||
|
## Mission Topology
|
||||||
|
|
||||||
|
Missions should align with dependency graph semantics:
|
||||||
|
|
||||||
|
- dependencies model execution order,
|
||||||
|
- independent missions can run in parallel,
|
||||||
|
- blocked missions must not be represented as ready work.
|
||||||
|
|
||||||
|
When topology changes, update bead dependency links first, then assignment communication.
|
||||||
|
|
||||||
|
## Realtime Contract
|
||||||
|
|
||||||
|
Realtime is the user visibility layer.
|
||||||
|
|
||||||
|
Expected sources:
|
||||||
|
|
||||||
|
- bead status updates,
|
||||||
|
- coordination events,
|
||||||
|
- reservation/lease changes,
|
||||||
|
- watcher/SSE refresh signals.
|
||||||
|
|
||||||
|
Expected outcomes:
|
||||||
|
|
||||||
|
- UI updates without manual refresh,
|
||||||
|
- consistent state across social/graph/session surfaces,
|
||||||
|
- event timeline continuity for audits.
|
||||||
|
|
||||||
|
## SSE/Event Behavior
|
||||||
|
|
||||||
|
Realtime streams should provide:
|
||||||
|
|
||||||
|
- monotonic event ids where supported,
|
||||||
|
- heartbeat behavior for long-lived connections,
|
||||||
|
- resilience to brief write bursts and file-watch jitter,
|
||||||
|
- eventual consistency with bead source of truth.
|
||||||
|
|
||||||
|
If stale-state is suspected, triage in this order:
|
||||||
|
|
||||||
|
1. Source-of-truth parity.
|
||||||
|
2. Read-path validation.
|
||||||
|
3. Watcher input coverage.
|
||||||
|
4. Event emission/subscription path.
|
||||||
|
|
||||||
|
## Agent Responsibilities
|
||||||
|
|
||||||
|
Agents must:
|
||||||
|
|
||||||
|
- emit meaningful coordination events during mission lifecycle,
|
||||||
|
- keep bead status and assignee current,
|
||||||
|
- provide verification evidence before close,
|
||||||
|
- avoid implicit/unlogged handoffs.
|
||||||
|
|
||||||
|
Agents must not:
|
||||||
|
|
||||||
|
- change BeadBoard UI project scope,
|
||||||
|
- rely on local assumptions not visible in event/state outputs.
|
||||||
|
|
||||||
|
## User Responsibilities
|
||||||
|
|
||||||
|
Users orchestrate control-plane actions in BeadBoard UI:
|
||||||
|
|
||||||
|
- scope selection,
|
||||||
|
- priority/assignment changes,
|
||||||
|
- intervention on blocked missions,
|
||||||
|
- monitoring mission and realtime health.
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
- Mission start without bead claim/assignee update.
|
||||||
|
- Hidden handoffs outside coordination events.
|
||||||
|
- Treating stale UI as resolved without parity checks.
|
||||||
|
- Closing missions without verification evidence.
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
## 1) Start Session
|
## 1) Start Session
|
||||||
|
|
||||||
1. Run preflight.
|
1. Run environment diagnosis.
|
||||||
2. Resolve bb path and confirm `bd` availability.
|
2. Run preflight.
|
||||||
3. Generate unique session agent name.
|
3. Resolve bb path and confirm `bd` availability.
|
||||||
4. Register agent identity.
|
4. Generate unique session agent name.
|
||||||
|
5. Register agent identity.
|
||||||
|
6. Confirm you are operating in the assigned target repository.
|
||||||
|
7. Do not change project scope (scope is user-controlled in BeadBoard UI).
|
||||||
|
|
||||||
## 2) Pick and Claim Work
|
## 2) Pick and Claim Work
|
||||||
|
|
||||||
|
|
|
||||||
79
skills/beadboard-driver/scripts/diagnose-env.mjs
Normal file
79
skills/beadboard-driver/scripts/diagnose-env.mjs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { findCommandInPath, resolveBbPath } from './lib/driver-lib.mjs';
|
||||||
|
|
||||||
|
async function pathExists(filePath) {
|
||||||
|
try {
|
||||||
|
await fs.access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const gitIndexLock = path.join(cwd, '.git', 'index.lock');
|
||||||
|
|
||||||
|
const findings = [];
|
||||||
|
const recommendations = [];
|
||||||
|
|
||||||
|
const bdPath = await findCommandInPath('bd');
|
||||||
|
const bb = await resolveBbPath();
|
||||||
|
const hasGitIndexLock = await pathExists(gitIndexLock);
|
||||||
|
|
||||||
|
if (!bdPath) {
|
||||||
|
findings.push({
|
||||||
|
code: 'BD_NOT_FOUND',
|
||||||
|
severity: 'high',
|
||||||
|
message: 'bd command not found in PATH.',
|
||||||
|
});
|
||||||
|
recommendations.push(
|
||||||
|
'Install BeadBoard tooling from https://github.com/zenchantlive/beadboard or add bd executable directory to PATH.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bb.ok) {
|
||||||
|
findings.push({
|
||||||
|
code: 'BB_NOT_FOUND',
|
||||||
|
severity: 'high',
|
||||||
|
message: bb.reason,
|
||||||
|
});
|
||||||
|
if (bb.remediation) {
|
||||||
|
recommendations.push(bb.remediation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasGitIndexLock) {
|
||||||
|
findings.push({
|
||||||
|
code: 'GIT_INDEX_LOCK_PRESENT',
|
||||||
|
severity: 'medium',
|
||||||
|
message: `Potential stale git lock detected at ${gitIndexLock}`,
|
||||||
|
});
|
||||||
|
recommendations.push('Run heal-common-issues.mjs with --apply --fix-git-index-lock if no git process is active.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
ok: findings.filter((item) => item.severity === 'high').length === 0,
|
||||||
|
timestamp,
|
||||||
|
environment: {
|
||||||
|
cwd,
|
||||||
|
project_root: cwd,
|
||||||
|
platform: process.platform,
|
||||||
|
node_version: process.version,
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
bd: { available: Boolean(bdPath), path: bdPath || null },
|
||||||
|
bb,
|
||||||
|
},
|
||||||
|
findings,
|
||||||
|
recommendations,
|
||||||
|
};
|
||||||
|
|
||||||
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main();
|
||||||
85
skills/beadboard-driver/scripts/ensure-project-context.mjs
Normal file
85
skills/beadboard-driver/scripts/ensure-project-context.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const args = new Set();
|
||||||
|
const values = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
const token = argv[i];
|
||||||
|
if (!token.startsWith('--')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const key = token.slice(2);
|
||||||
|
const next = argv[i + 1];
|
||||||
|
if (!next || next.startsWith('--')) {
|
||||||
|
args.add(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
values[key] = next;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { args, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exists(filePath) {
|
||||||
|
try {
|
||||||
|
await fs.access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { values } = parseArgs(process.argv.slice(2));
|
||||||
|
const projectRoot = values['project-root'] ? path.resolve(values['project-root']) : process.cwd();
|
||||||
|
|
||||||
|
const thisFile = fileURLToPath(import.meta.url);
|
||||||
|
const skillRoot = path.resolve(path.dirname(thisFile), '..');
|
||||||
|
const templatePath = path.join(skillRoot, 'project.template.md');
|
||||||
|
const targetPath = path.join(projectRoot, 'project.md');
|
||||||
|
|
||||||
|
const targetExists = await exists(targetPath);
|
||||||
|
if (targetExists) {
|
||||||
|
process.stdout.write(
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
ok: true,
|
||||||
|
created: false,
|
||||||
|
used_existing: true,
|
||||||
|
project_root: projectRoot,
|
||||||
|
target_path: targetPath,
|
||||||
|
template_path: templatePath,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}\n`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = await fs.readFile(templatePath, 'utf8');
|
||||||
|
await fs.writeFile(targetPath, template, 'utf8');
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
ok: true,
|
||||||
|
created: true,
|
||||||
|
used_existing: false,
|
||||||
|
project_root: projectRoot,
|
||||||
|
target_path: targetPath,
|
||||||
|
template_path: templatePath,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main();
|
||||||
104
skills/beadboard-driver/scripts/heal-common-issues.mjs
Normal file
104
skills/beadboard-driver/scripts/heal-common-issues.mjs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const args = new Set();
|
||||||
|
const values = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
const token = argv[i];
|
||||||
|
if (!token.startsWith('--')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const key = token.slice(2);
|
||||||
|
const next = argv[i + 1];
|
||||||
|
if (!next || next.startsWith('--')) {
|
||||||
|
args.add(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
values[key] = next;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { args, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pathExists(filePath) {
|
||||||
|
try {
|
||||||
|
await fs.access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeIfExists(filePath) {
|
||||||
|
if (!(await pathExists(filePath))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await fs.rm(filePath, { force: true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { args, values } = parseArgs(process.argv.slice(2));
|
||||||
|
const apply = args.has('apply');
|
||||||
|
const fixGitIndexLock = args.has('fix-git-index-lock');
|
||||||
|
const projectRoot = values['project-root'] ? path.resolve(values['project-root']) : process.cwd();
|
||||||
|
|
||||||
|
const actions = [];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
const lockPath = path.join(projectRoot, '.git', 'index.lock');
|
||||||
|
const lockExists = await pathExists(lockPath);
|
||||||
|
|
||||||
|
if (fixGitIndexLock && lockExists) {
|
||||||
|
if (apply) {
|
||||||
|
const removed = await removeIfExists(lockPath);
|
||||||
|
actions.push({
|
||||||
|
id: 'fix-git-index-lock',
|
||||||
|
attempted: true,
|
||||||
|
applied: removed,
|
||||||
|
target: lockPath,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
actions.push({
|
||||||
|
id: 'fix-git-index-lock',
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
target: lockPath,
|
||||||
|
});
|
||||||
|
warnings.push('Dry-run mode enabled. Re-run with --apply to perform fixes.');
|
||||||
|
}
|
||||||
|
} else if (fixGitIndexLock && !lockExists) {
|
||||||
|
actions.push({
|
||||||
|
id: 'fix-git-index-lock',
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
target: lockPath,
|
||||||
|
note: 'No index.lock found.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fixGitIndexLock && lockExists) {
|
||||||
|
warnings.push('Stale git index.lock detected. Use --fix-git-index-lock to target it.');
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
ok: true,
|
||||||
|
mode: apply ? 'apply' : 'dry-run',
|
||||||
|
project_root: projectRoot,
|
||||||
|
actions,
|
||||||
|
warnings,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main();
|
||||||
24
skills/beadboard-driver/tests/diagnose-env.contract.test.mjs
Normal file
24
skills/beadboard-driver/tests/diagnose-env.contract.test.mjs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'diagnose-env.mjs');
|
||||||
|
|
||||||
|
test('diagnose-env contract: returns stable schema', async () => {
|
||||||
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath], {
|
||||||
|
env: { ...process.env, PATH: '' },
|
||||||
|
});
|
||||||
|
const result = JSON.parse(stdout);
|
||||||
|
|
||||||
|
assert.equal(typeof result.ok, 'boolean');
|
||||||
|
assert.equal(typeof result.timestamp, 'string');
|
||||||
|
assert.equal(result.environment !== null, true);
|
||||||
|
assert.equal(Array.isArray(result.findings), true);
|
||||||
|
assert.equal(Array.isArray(result.recommendations), true);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'ensure-project-context.mjs');
|
||||||
|
|
||||||
|
test('ensure-project-context creates project.md when missing', async () => {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-project-context-'));
|
||||||
|
try {
|
||||||
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--project-root', root]);
|
||||||
|
const result = JSON.parse(stdout);
|
||||||
|
|
||||||
|
const content = await fs.readFile(path.join(root, 'project.md'), 'utf8');
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.created, true);
|
||||||
|
assert.match(content, /Project Driver Template/);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ensure-project-context preserves existing project.md', async () => {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-project-context-'));
|
||||||
|
try {
|
||||||
|
const target = path.join(root, 'project.md');
|
||||||
|
await fs.writeFile(target, '# existing\n', 'utf8');
|
||||||
|
|
||||||
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--project-root', root]);
|
||||||
|
const result = JSON.parse(stdout);
|
||||||
|
const content = await fs.readFile(target, 'utf8');
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.created, false);
|
||||||
|
assert.equal(result.used_existing, true);
|
||||||
|
assert.equal(content, '# existing\n');
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'heal-common-issues.mjs');
|
||||||
|
|
||||||
|
test('heal-common-issues contract: dry-run does not mutate git index.lock', async () => {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-heal-'));
|
||||||
|
try {
|
||||||
|
const lockDir = path.join(root, '.git');
|
||||||
|
const lockPath = path.join(lockDir, 'index.lock');
|
||||||
|
await fs.mkdir(lockDir, { recursive: true });
|
||||||
|
await fs.writeFile(lockPath, 'locked', 'utf8');
|
||||||
|
|
||||||
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--project-root', root], {
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
const result = JSON.parse(stdout);
|
||||||
|
|
||||||
|
const lockStillExists = await fs
|
||||||
|
.access(lockPath)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.mode, 'dry-run');
|
||||||
|
assert.equal(lockStillExists, true);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('heal-common-issues contract: apply removes stale git index.lock when opted in', async () => {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-heal-'));
|
||||||
|
try {
|
||||||
|
const lockDir = path.join(root, '.git');
|
||||||
|
const lockPath = path.join(lockDir, 'index.lock');
|
||||||
|
await fs.mkdir(lockDir, { recursive: true });
|
||||||
|
await fs.writeFile(lockPath, 'locked', 'utf8');
|
||||||
|
|
||||||
|
const { stdout } = await execFileAsync(
|
||||||
|
process.execPath,
|
||||||
|
[scriptPath, '--project-root', root, '--apply', '--fix-git-index-lock'],
|
||||||
|
{
|
||||||
|
env: process.env,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const result = JSON.parse(stdout);
|
||||||
|
|
||||||
|
const lockStillExists = await fs
|
||||||
|
.access(lockPath)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.mode, 'apply');
|
||||||
|
assert.equal(lockStillExists, false);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
import { promisify } from 'node:util';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'readiness-report.mjs');
|
||||||
|
|
||||||
|
test('readiness-report contract: returns ready true for passing checks and present artifacts', async () => {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-readiness-'));
|
||||||
|
try {
|
||||||
|
const artifact = path.join(root, 'artifact.txt');
|
||||||
|
await fs.writeFile(artifact, 'ok', 'utf8');
|
||||||
|
|
||||||
|
const checks = JSON.stringify([
|
||||||
|
{ name: 'typecheck', ok: true, details: 'pass' },
|
||||||
|
{ name: 'lint', ok: true, details: 'pass' },
|
||||||
|
]);
|
||||||
|
const artifacts = JSON.stringify([{ path: artifact, required: true }]);
|
||||||
|
|
||||||
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--checks', checks, '--artifacts', artifacts]);
|
||||||
|
const result = JSON.parse(stdout);
|
||||||
|
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.equal(result.summary.ready, true);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -11,6 +11,10 @@ const tests = [
|
||||||
path.join(__dirname, 'resolve-bb.contract.test.mjs'),
|
path.join(__dirname, 'resolve-bb.contract.test.mjs'),
|
||||||
path.join(__dirname, 'generate-agent-name.contract.test.mjs'),
|
path.join(__dirname, 'generate-agent-name.contract.test.mjs'),
|
||||||
path.join(__dirname, 'session-preflight.contract.test.mjs'),
|
path.join(__dirname, 'session-preflight.contract.test.mjs'),
|
||||||
|
path.join(__dirname, 'readiness-report.contract.test.mjs'),
|
||||||
|
path.join(__dirname, 'diagnose-env.contract.test.mjs'),
|
||||||
|
path.join(__dirname, 'heal-common-issues.contract.test.mjs'),
|
||||||
|
path.join(__dirname, 'ensure-project-context.contract.test.mjs'),
|
||||||
];
|
];
|
||||||
|
|
||||||
const child = spawn(process.execPath, ['--test', ...tests], {
|
const child = spawn(process.execPath, ['--test', ...tests], {
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,17 @@ test('session-preflight contract: succeeds with bd + BB_REPO', async () => {
|
||||||
try {
|
try {
|
||||||
const repo = path.join(root, 'beadboard');
|
const repo = path.join(root, 'beadboard');
|
||||||
const toolsDir = path.join(root, 'tools');
|
const toolsDir = path.join(root, 'tools');
|
||||||
|
const bdExecutable = process.platform === 'win32' ? 'bd.cmd' : 'bd';
|
||||||
|
const bdPath = path.join(toolsDir, bdExecutable);
|
||||||
await fs.mkdir(path.join(repo, 'tools'), { recursive: true });
|
await fs.mkdir(path.join(repo, 'tools'), { recursive: true });
|
||||||
await fs.mkdir(toolsDir, { recursive: true });
|
await fs.mkdir(toolsDir, { recursive: true });
|
||||||
await fs.writeFile(path.join(repo, 'bb.ps1'), 'echo ok', 'utf8');
|
await fs.writeFile(path.join(repo, 'bb.ps1'), 'echo ok', 'utf8');
|
||||||
await fs.writeFile(path.join(toolsDir, 'bd.cmd'), '@echo off\r\necho beads\r\n', 'utf8');
|
if (process.platform === 'win32') {
|
||||||
|
await fs.writeFile(bdPath, '@echo off\r\necho beads\r\n', 'utf8');
|
||||||
|
} else {
|
||||||
|
await fs.writeFile(bdPath, '#!/usr/bin/env sh\necho beads\n', 'utf8');
|
||||||
|
await fs.chmod(bdPath, 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
const { stdout } = await execFileAsync(process.execPath, [scriptPath], {
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath], {
|
||||||
env: { ...process.env, PATH: toolsDir, BB_REPO: repo, BB_SKILL_HOME: path.join(root, 'home') },
|
env: { ...process.env, PATH: toolsDir, BB_REPO: repo, BB_SKILL_HOME: path.join(root, 'home') },
|
||||||
|
|
|
||||||
1
skills/shadcn-ui
Symbolic link
1
skills/shadcn-ui
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,306 +0,0 @@
|
||||||
### shadcn/ui Chart Component - Installation
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest add chart
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Basic Usage
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", desktop: 186, mobile: 80 },
|
|
||||||
{ month: "February", desktop: 305, mobile: 200 },
|
|
||||||
{ month: "March", desktop: 237, mobile: 120 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function BarChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
||||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - ChartConfig with Custom Colors
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
You can define custom colors directly in the configuration using hex values or CSS variables.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "#2563eb",
|
|
||||||
theme: {
|
|
||||||
light: "#2563eb",
|
|
||||||
dark: "#60a5fa",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - CSS Variables
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Add chart color variables to your globals.css for consistent theming.
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Chart colors */
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.546 0.198 38.228);
|
|
||||||
--chart-4: oklch(0.596 0.151 343.253);
|
|
||||||
--chart-5: oklch(0.546 0.158 49.157);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.698 0.141 24.311);
|
|
||||||
--chart-4: oklch(0.676 0.172 171.196);
|
|
||||||
--chart-5: oklch(0.578 0.192 302.85);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Line Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a line chart with shadcn/ui charts component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
price: {
|
|
||||||
label: "Price",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const chartData = [
|
|
||||||
{ month: "January", price: 186 },
|
|
||||||
{ month: "February", price: 305 },
|
|
||||||
{ month: "March", price: 237 },
|
|
||||||
{ month: "April", price: 203 },
|
|
||||||
{ month: "May", price: 276 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function LineChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
|
|
||||||
<Line
|
|
||||||
dataKey="price"
|
|
||||||
stroke="var(--color-price)"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Area Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating an area chart with gradient fill and legend.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Area, AreaChart, XAxis, YAxis } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
|
||||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
export function AreaChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<AreaChart data={chartData}>
|
|
||||||
<XAxis dataKey="month" tickLine={false} axisLine={false} />
|
|
||||||
<YAxis tickLine={false} axisLine={false} />
|
|
||||||
<Area
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
stroke="var(--color-desktop)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
stroke="var(--color-mobile)"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Pie Chart Example
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Creating a pie/donut chart with shadcn/ui.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Pie, PieChart } from "recharts"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/components/ui/chart"
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
chrome: { label: "Chrome", color: "var(--chart-1)" },
|
|
||||||
safari: { label: "Safari", color: "var(--chart-2)" },
|
|
||||||
firefox: { label: "Firefox", color: "var(--chart-3)" },
|
|
||||||
} satisfies import("@/components/ui/chart").ChartConfig
|
|
||||||
|
|
||||||
const pieData = [
|
|
||||||
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
|
|
||||||
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
|
|
||||||
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PieChartDemo() {
|
|
||||||
return (
|
|
||||||
<ChartContainer config={chartConfig} className="min-h-[200px]">
|
|
||||||
<PieChart>
|
|
||||||
<Pie
|
|
||||||
data={pieData}
|
|
||||||
dataKey="visitors"
|
|
||||||
nameKey="browser"
|
|
||||||
cx="50%"
|
|
||||||
cy="50%"
|
|
||||||
outerRadius={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<ChartLegend content={<ChartLegendContent />} />
|
|
||||||
</PieChart>
|
|
||||||
</ChartContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui ChartTooltipContent Props
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
|
|
||||||
|
|
||||||
| Prop | Type | Default | Description |
|
|
||||||
|------|------|---------|-------------|
|
|
||||||
| `labelKey` | string | "label" | Key for tooltip label |
|
|
||||||
| `nameKey` | string | "name" | Key for tooltip name |
|
|
||||||
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
|
|
||||||
| `hideLabel` | boolean | false | Hide label |
|
|
||||||
| `hideIndicator` | boolean | false | Hide indicator |
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Accessibility
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<BarChart accessibilityLayer data={chartData}>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis dataKey="month" />
|
|
||||||
<Bar dataKey="desktop" fill="var(--color-desktop)" />
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
</BarChart>
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
- Keyboard arrow key navigation
|
|
||||||
- ARIA labels for chart elements
|
|
||||||
- Screen reader announcements for data values
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
### shadcn/ui Chart Component - Recharts Dependencies
|
|
||||||
|
|
||||||
Source: https://ui.shadcn.com/docs/components/chart
|
|
||||||
|
|
||||||
The chart component requires the following Recharts dependencies to be installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add recharts
|
|
||||||
npm install recharts
|
|
||||||
yarn add recharts
|
|
||||||
```
|
|
||||||
|
|
||||||
Recharts provides the following chart types:
|
|
||||||
- Area, Bar, Line, Pie, Composed
|
|
||||||
- Radar, RadialBar, Scatter
|
|
||||||
- Funnel, Treemap
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# shadcn/ui Learning Guide
|
|
||||||
|
|
||||||
This guide helps you learn shadcn/ui from basics to advanced patterns.
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
### 1. Understanding the Philosophy
|
|
||||||
|
|
||||||
shadcn/ui is different from traditional component libraries:
|
|
||||||
|
|
||||||
- **Copy-paste components**: Components are copied into your project, not installed as packages
|
|
||||||
- **Full customization**: You own the code and can modify it freely
|
|
||||||
- **Built on Radix UI**: Provides accessibility primitives
|
|
||||||
- **Styled with Tailwind**: Uses utility classes for consistent styling
|
|
||||||
|
|
||||||
### 2. Core Concepts to Master
|
|
||||||
|
|
||||||
#### Class Variance Authority (CVA)
|
|
||||||
Most components use CVA for variant management:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"base-classes",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "variant-classes",
|
|
||||||
destructive: "destructive-classes",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "size-classes",
|
|
||||||
sm: "small-classes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cn Utility Function
|
|
||||||
The `cn` function combines classes and resolves conflicts:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation Checklist
|
|
||||||
|
|
||||||
- [ ] Initialize a new project (Next.js, Vite, or Remix)
|
|
||||||
- [ ] Install Tailwind CSS
|
|
||||||
- [ ] Run `npx shadcn@latest init`
|
|
||||||
- [ ] Configure CSS variables
|
|
||||||
- [ ] Install first component: `npx shadcn@latest add button`
|
|
||||||
|
|
||||||
### 4. Essential Components to Learn First
|
|
||||||
|
|
||||||
1. **Button** - Learn variants and sizes
|
|
||||||
2. **Input** - Form inputs with labels
|
|
||||||
3. **Card** - Container components
|
|
||||||
4. **Form** - Form handling with React Hook Form
|
|
||||||
5. **Dialog** - Modal windows
|
|
||||||
6. **Select** - Dropdown selections
|
|
||||||
7. **Toast** - Notifications
|
|
||||||
|
|
||||||
### 5. Common Patterns
|
|
||||||
|
|
||||||
#### Form Pattern
|
|
||||||
Every form follows this structure:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
1. Define Zod schema
|
|
||||||
2. Create form with useForm
|
|
||||||
3. Wrap with Form component
|
|
||||||
4. Add FormField for each input
|
|
||||||
5. Handle submission
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Component Customization Pattern
|
|
||||||
To customize a component:
|
|
||||||
|
|
||||||
1. Copy component to your project
|
|
||||||
2. Modify the variants
|
|
||||||
3. Add new props if needed
|
|
||||||
4. Update types
|
|
||||||
|
|
||||||
### 6. Best Practices
|
|
||||||
|
|
||||||
- Always use TypeScript
|
|
||||||
- Follow the existing component structure
|
|
||||||
- Use semantic HTML when possible
|
|
||||||
- Test with screen readers for accessibility
|
|
||||||
- Keep components small and focused
|
|
||||||
|
|
||||||
### 7. Advanced Topics
|
|
||||||
|
|
||||||
- Creating custom components from scratch
|
|
||||||
- Building complex forms with validation
|
|
||||||
- Implementing dark mode
|
|
||||||
- Optimizing for performance
|
|
||||||
- Testing components
|
|
||||||
|
|
||||||
## Practice Exercises
|
|
||||||
|
|
||||||
### Exercise 1: Basic Setup
|
|
||||||
1. Create a new Next.js project
|
|
||||||
2. Set up shadcn/ui
|
|
||||||
3. Install and customize a Button component
|
|
||||||
4. Add a new variant "gradient"
|
|
||||||
|
|
||||||
### Exercise 2: Form Building
|
|
||||||
1. Create a contact form with:
|
|
||||||
- Name input (required)
|
|
||||||
- Email input (email validation)
|
|
||||||
- Message textarea (min length)
|
|
||||||
- Submit button with loading state
|
|
||||||
|
|
||||||
### Exercise 3: Component Combination
|
|
||||||
1. Build a settings page using:
|
|
||||||
- Card for layout
|
|
||||||
- Sheet for mobile menu
|
|
||||||
- Select for dropdowns
|
|
||||||
- Switch for toggles
|
|
||||||
- Toast for notifications
|
|
||||||
|
|
||||||
### Exercise 4: Custom Component
|
|
||||||
1. Create a custom Badge component
|
|
||||||
2. Support variants: default, secondary, destructive, outline
|
|
||||||
3. Support sizes: sm, default, lg
|
|
||||||
4. Add icon support
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Official Documentation](https://ui.shadcn.com)
|
|
||||||
- [GitHub Repository](https://github.com/shadcn/ui)
|
|
||||||
- [Examples Gallery](https://ui.shadcn.com/examples)
|
|
||||||
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
|
|
||||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,586 +0,0 @@
|
||||||
# shadcn.io Component Library
|
|
||||||
|
|
||||||
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
|
|
||||||
|
|
||||||
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
|
|
||||||
|
|
||||||
## Core Components
|
|
||||||
|
|
||||||
### Terminal Component
|
|
||||||
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Terminal } from "@/components/ui/terminal"
|
|
||||||
|
|
||||||
export default function DemoTerminal() {
|
|
||||||
return (
|
|
||||||
npm install @repo/terminalInstalling dependencies...npm start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dock Component
|
|
||||||
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Dock, DockIcon } from "@/components/ui/dock"
|
|
||||||
import { Home, Settings, User, Mail } from "lucide-react"
|
|
||||||
|
|
||||||
export default function AppDock() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Credit Card Component
|
|
||||||
Interactive 3D credit card component with flip animations for payment forms and card displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { CreditCard } from "@/components/ui/credit-card"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function PaymentForm() {
|
|
||||||
const [cardData, setCardData] = useState({
|
|
||||||
number: "4532 1234 5678 9010",
|
|
||||||
holder: "JOHN DOE",
|
|
||||||
expiry: "12/28",
|
|
||||||
cvv: "123"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
console.log("Card flipped:", flipped)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Zoom Component
|
|
||||||
Zoomable image component with smooth modal transitions for image galleries and product displays.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ImageZoom } from "@/components/ui/image-zoom"
|
|
||||||
|
|
||||||
export default function ProductGallery() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Code Component
|
|
||||||
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { QRCode } from "@/components/ui/qr-code"
|
|
||||||
|
|
||||||
export default function ShareDialog() {
|
|
||||||
const shareUrl = "https://shadcn.io"
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Scan to visit shadcn.io
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Picker Component
|
|
||||||
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ColorPicker } from "@/components/ui/color-picker"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export default function ThemeCustomizer() {
|
|
||||||
const [color, setColor] = useState("#3b82f6")
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
Selected: {color}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chart Components
|
|
||||||
|
|
||||||
### Bar Chart Component
|
|
||||||
Clean bar chart component for data comparison and categorical analysis using Recharts.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { BarChart } from "@/components/ui/bar-chart"
|
|
||||||
|
|
||||||
export default function SalesChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", sales: 4000, revenue: 2400 },
|
|
||||||
{ month: "Feb", sales: 3000, revenue: 1398 },
|
|
||||||
{ month: "Mar", sales: 2000, revenue: 9800 },
|
|
||||||
{ month: "Apr", sales: 2780, revenue: 3908 },
|
|
||||||
{ month: "May", sales: 1890, revenue: 4800 },
|
|
||||||
{ month: "Jun", sales: 2390, revenue: 3800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`$${value.toLocaleString()}`}
|
|
||||||
yAxisWidth={60}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Line Chart Component
|
|
||||||
Smooth line chart for visualizing trends and time-series data with multiple data series support.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LineChart } from "@/components/ui/line-chart"
|
|
||||||
|
|
||||||
export default function MetricsChart() {
|
|
||||||
const data = [
|
|
||||||
{ date: "2024-01", users: 1200, sessions: 3400 },
|
|
||||||
{ date: "2024-02", users: 1800, sessions: 4200 },
|
|
||||||
{ date: "2024-03", users: 2400, sessions: 5800 },
|
|
||||||
{ date: "2024-04", users: 3100, sessions: 7200 },
|
|
||||||
{ date: "2024-05", users: 3800, sessions: 8900 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Component
|
|
||||||
Donut chart component for displaying proportional data and percentage distributions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { PieChart } from "@/components/ui/pie-chart"
|
|
||||||
|
|
||||||
export default function MarketShareChart() {
|
|
||||||
const data = [
|
|
||||||
{ name: "Product A", value: 400, fill: "#3b82f6" },
|
|
||||||
{ name: "Product B", value: 300, fill: "#10b981" },
|
|
||||||
{ name: "Product C", value: 300, fill: "#f59e0b" },
|
|
||||||
{ name: "Product D", value: 200, fill: "#ef4444" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${entry.name}: ${entry.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Area Chart Component
|
|
||||||
Stacked area chart for visualizing volume changes over time with multiple data series.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AreaChart } from "@/components/ui/area-chart"
|
|
||||||
|
|
||||||
export default function TrafficChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
|
|
||||||
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
|
|
||||||
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
|
|
||||||
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
|
|
||||||
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Radar Chart Component
|
|
||||||
Multi-axis chart for comparing multiple variables across different categories simultaneously.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RadarChart } from "@/components/ui/radar-chart"
|
|
||||||
|
|
||||||
export default function SkillsChart() {
|
|
||||||
const data = [
|
|
||||||
{ skill: "JavaScript", score: 85, industry: 75 },
|
|
||||||
{ skill: "TypeScript", score: 80, industry: 70 },
|
|
||||||
{ skill: "React", score: 90, industry: 80 },
|
|
||||||
{ skill: "Node.js", score: 75, industry: 72 },
|
|
||||||
{ skill: "CSS", score: 88, industry: 78 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mixed Chart Component
|
|
||||||
Combined bar and line chart for displaying multiple data types with different visualization methods.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MixedChart } from "@/components/ui/mixed-chart"
|
|
||||||
|
|
||||||
export default function PerformanceChart() {
|
|
||||||
const data = [
|
|
||||||
{ month: "Jan", revenue: 4000, growth: 5.2 },
|
|
||||||
{ month: "Feb", revenue: 4200, growth: 5.0 },
|
|
||||||
{ month: "Mar", revenue: 4800, growth: 14.3 },
|
|
||||||
{ month: "Apr", revenue: 5200, growth: 8.3 },
|
|
||||||
{ month: "May", revenue: 5800, growth: 11.5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Animation Components
|
|
||||||
|
|
||||||
### Magnetic Effect Component
|
|
||||||
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Magnetic } from "@/components/ui/magnetic"
|
|
||||||
|
|
||||||
export default function InteractiveButton() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
Hover me
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Animated Cursor Component
|
|
||||||
Custom animated cursor with interactive effects and particle trails for immersive experiences.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AnimatedCursor } from "@/components/ui/animated-cursor"
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Apple Hello Effect Component
|
|
||||||
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { AppleHello } from "@/components/ui/apple-hello"
|
|
||||||
|
|
||||||
export default function WelcomeScreen() {
|
|
||||||
const greetings = [
|
|
||||||
{ text: "Hello", lang: "en" },
|
|
||||||
{ text: "Bonjour", lang: "fr" },
|
|
||||||
{ text: "こんにちは", lang: "ja" },
|
|
||||||
{ text: "Hola", lang: "es" },
|
|
||||||
{ text: "你好", lang: "zh" }
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Liquid Button Component
|
|
||||||
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { LiquidButton } from "@/components/ui/liquid-button"
|
|
||||||
|
|
||||||
export default function CTASection() {
|
|
||||||
return (
|
|
||||||
console.log("CTA clicked")}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Text Component
|
|
||||||
Text animation that creates a rolling effect with smooth character transitions.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { RollingText } from "@/components/ui/rolling-text"
|
|
||||||
|
|
||||||
export default function AnimatedHeading() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shimmering Text Component
|
|
||||||
Text with animated shimmer effect for attention-grabbing headings and highlights.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ShimmeringText } from "@/components/ui/shimmering-text"
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
return (
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## React Hooks
|
|
||||||
|
|
||||||
### useBoolean Hook
|
|
||||||
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useBoolean } from "@/hooks/use-boolean"
|
|
||||||
|
|
||||||
export default function TogglePanel() {
|
|
||||||
const modal = useBoolean(false)
|
|
||||||
const loading = useBoolean(false)
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
loading.setTrue()
|
|
||||||
try {
|
|
||||||
await submitForm()
|
|
||||||
modal.setFalse()
|
|
||||||
} finally {
|
|
||||||
loading.setFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Toggle Modal
|
|
||||||
{modal.value && (
|
|
||||||
|
|
||||||
|
|
||||||
Status: {loading.value ? "Saving..." : "Ready"}
|
|
||||||
|
|
||||||
Submit
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCounter Hook
|
|
||||||
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCounter } from "@/hooks/use-counter"
|
|
||||||
|
|
||||||
export default function CartCounter() {
|
|
||||||
const quantity = useCounter(0, { min: 0, max: 99 })
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
-
|
|
||||||
{quantity.value}
|
|
||||||
+
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useLocalStorage Hook
|
|
||||||
Persist state in browser localStorage with automatic serialization and deserialization.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useLocalStorage } from "@/hooks/use-local-storage"
|
|
||||||
|
|
||||||
export default function UserPreferences() {
|
|
||||||
const [theme, setTheme] = useLocalStorage("theme", "light")
|
|
||||||
const [settings, setSettings] = useLocalStorage("settings", {
|
|
||||||
notifications: true,
|
|
||||||
emailUpdates: false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(e.target.value)}>
|
|
||||||
LightDark setSettings({
|
|
||||||
...settings,
|
|
||||||
notifications: e.target.checked
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
Enable Notifications
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useDebounceValue Hook
|
|
||||||
Debounce values to prevent excessive updates and API calls during rapid user input.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useDebounceValue } from "@/hooks/use-debounce-value"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
|
|
||||||
export default function SearchBox() {
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const debouncedSearch = useDebounceValue(search, 500)
|
|
||||||
const [results, setResults] = useState([])
|
|
||||||
const [apiCalls, setApiCalls] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
setApiCalls(prev => prev + 1)
|
|
||||||
fetch(`/api/search?q=${debouncedSearch}`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setResults)
|
|
||||||
}
|
|
||||||
}, [debouncedSearch])
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
setSearch(e.target.value)}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
API calls: {apiCalls}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useHover Hook
|
|
||||||
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useHover } from "@/hooks/use-hover"
|
|
||||||
import { useRef } from "react"
|
|
||||||
|
|
||||||
export default function ImagePreview() {
|
|
||||||
const hoverRef = useRef(null)
|
|
||||||
const isHovering = useHover(hoverRef, {
|
|
||||||
enterDelay: 200,
|
|
||||||
leaveDelay: 100
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
{isHovering && (
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useCountdown Hook
|
|
||||||
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useCountdown } from "@/hooks/use-countdown"
|
|
||||||
|
|
||||||
export default function OTPTimer() {
|
|
||||||
const countdown = useCountdown({
|
|
||||||
initialSeconds: 60,
|
|
||||||
onComplete: () => alert("OTP expired! Request a new code.")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
|
||||||
{countdown.seconds}s
|
|
||||||
|
|
||||||
{!countdown.isRunning ? (
|
|
||||||
Start
|
|
||||||
) : (
|
|
||||||
Pause
|
|
||||||
)}
|
|
||||||
Reset
|
|
||||||
|
|
||||||
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation and Usage
|
|
||||||
|
|
||||||
### CLI Installation
|
|
||||||
Install components directly into your project using the shadcn CLI for instant integration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize shadcn in your project
|
|
||||||
npx shadcn@latest init
|
|
||||||
|
|
||||||
# Add individual components
|
|
||||||
npx shadcn@latest add terminal
|
|
||||||
npx shadcn@latest add dock
|
|
||||||
npx shadcn@latest add credit-card
|
|
||||||
|
|
||||||
# Add multiple components at once
|
|
||||||
npx shadcn@latest add bar-chart line-chart pie-chart
|
|
||||||
|
|
||||||
# Add hooks
|
|
||||||
npx shadcn@latest add use-boolean use-counter use-local-storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Configuration
|
|
||||||
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tailwind.config.ts
|
|
||||||
import type { Config } from "tailwindcss"
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./pages/**/*.{ts,tsx}",
|
|
||||||
"./components/**/*.{ts,tsx}",
|
|
||||||
"./app/**/*.{ts,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
|
|
||||||
|
|
||||||
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,8 @@ import { readIssuesForScope } from '../lib/aggregate-read';
|
||||||
import { resolveProjectScope } from '../lib/project-scope';
|
import { resolveProjectScope } from '../lib/project-scope';
|
||||||
import { listProjects } from '../lib/registry';
|
import { listProjects } from '../lib/registry';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { TaskCardGrid, type BlockerDetail } from './task-card-grid';
|
||||||
import { TaskDetailsDrawer } from './task-details-drawer';
|
import { TaskDetailsDrawer } from './task-details-drawer';
|
||||||
import { DependencyFlowStrip } from './dependency-flow-strip';
|
import { DependencyFlowStrip } from './dependency-flow-strip';
|
||||||
import { GraphNodeCard, type GraphNodeData } from './graph-node-card';
|
import { GraphNodeCard, type GraphNodeData } from './graph-node-card';
|
||||||
|
import { OffsetEdge } from './offset-edge';
|
||||||
import { GraphSection } from './graph-section';
|
import { GraphSection } from './graph-section';
|
||||||
import { ProjectScopeControls } from '../shared/project-scope-controls';
|
import { ProjectScopeControls } from '../shared/project-scope-controls';
|
||||||
import { WorkspaceHero } from '../shared/workspace-hero';
|
import { WorkspaceHero } from '../shared/workspace-hero';
|
||||||
|
|
@ -31,6 +32,7 @@ import {
|
||||||
type GraphHopDepth,
|
type GraphHopDepth,
|
||||||
analyzeBlockedChain,
|
analyzeBlockedChain,
|
||||||
detectDependencyCycles,
|
detectDependencyCycles,
|
||||||
|
identifyTransitiveEdges,
|
||||||
} from '../../lib/graph-view';
|
} from '../../lib/graph-view';
|
||||||
import { buildBlockedByTree } from '../../lib/kanban';
|
import { buildBlockedByTree } from '../../lib/kanban';
|
||||||
import { type BeadIssue } from '../../lib/types';
|
import { type BeadIssue } from '../../lib/types';
|
||||||
|
|
@ -167,6 +169,10 @@ export function DependencyGraphPage({
|
||||||
}),
|
}),
|
||||||
[issues, hideClosed],
|
[issues, hideClosed],
|
||||||
);
|
);
|
||||||
|
const selectableEpics = useMemo(
|
||||||
|
() => epics.filter((epic) => (!hideClosed ? true : epic.status !== 'closed' && epic.status !== 'tombstone')),
|
||||||
|
[epics, hideClosed],
|
||||||
|
);
|
||||||
|
|
||||||
// --- Derived data: tasks grouped by parent epic ---
|
// --- Derived data: tasks grouped by parent epic ---
|
||||||
const tasksByEpic = useMemo(() => {
|
const tasksByEpic = useMemo(() => {
|
||||||
|
|
@ -226,18 +232,18 @@ export function DependencyGraphPage({
|
||||||
|
|
||||||
// --- Auto-select first epic if none selected ---
|
// --- Auto-select first epic if none selected ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (epics.length === 0) {
|
if (selectableEpics.length === 0) {
|
||||||
if (selectedEpicId !== null) {
|
if (selectedEpicId !== null) {
|
||||||
setSelectedEpicId(null);
|
setSelectedEpicId(null);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasSelectedEpic = selectedEpicId ? epics.some((epic) => epic.id === selectedEpicId) : false;
|
const hasSelectedEpic = selectedEpicId ? selectableEpics.some((epic) => epic.id === selectedEpicId) : false;
|
||||||
if (!hasSelectedEpic) {
|
if (!hasSelectedEpic) {
|
||||||
setSelectedEpicId(epics[0].id);
|
setSelectedEpicId(selectableEpics[0].id);
|
||||||
}
|
}
|
||||||
}, [epics, selectedEpicId]);
|
}, [selectableEpics, selectedEpicId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (requestedTab === 'tasks' || requestedTab === 'dependencies') {
|
if (requestedTab === 'tasks' || requestedTab === 'dependencies') {
|
||||||
|
|
@ -247,9 +253,9 @@ export function DependencyGraphPage({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!requestedEpicId) return;
|
if (!requestedEpicId) return;
|
||||||
if (!epics.some((epic) => epic.id === requestedEpicId)) return;
|
if (!selectableEpics.some((epic) => epic.id === requestedEpicId)) return;
|
||||||
setSelectedEpicId(requestedEpicId);
|
setSelectedEpicId(requestedEpicId);
|
||||||
}, [epics, requestedEpicId]);
|
}, [selectableEpics, requestedEpicId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!requestedTaskId) {
|
if (!requestedTaskId) {
|
||||||
|
|
@ -272,7 +278,7 @@ export function DependencyGraphPage({
|
||||||
}, [issues, selectedId]);
|
}, [issues, selectedId]);
|
||||||
|
|
||||||
// --- Derived: selected epic and its tasks ---
|
// --- Derived: selected epic and its tasks ---
|
||||||
const selectedEpic = useMemo(() => epics.find((epic) => epic.id === selectedEpicId) ?? null, [epics, selectedEpicId]);
|
const selectedEpic = useMemo(() => selectableEpics.find((epic) => epic.id === selectedEpicId) ?? null, [selectableEpics, selectedEpicId]);
|
||||||
const projectLevelTasks = useMemo(
|
const projectLevelTasks = useMemo(
|
||||||
() =>
|
() =>
|
||||||
issues
|
issues
|
||||||
|
|
@ -301,8 +307,8 @@ export function DependencyGraphPage({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last-resort fallback: if there are only epics, render epics as selectable items.
|
// Last-resort fallback: if there are only epics, render epics as selectable items.
|
||||||
return epics.filter((epic) => (!hideClosed ? true : epic.status !== 'closed'));
|
return selectableEpics;
|
||||||
}, [epics, hideClosed, projectLevelTasks, selectedEpic, tasksByEpic]);
|
}, [projectLevelTasks, selectableEpics, selectedEpic, tasksByEpic]);
|
||||||
|
|
||||||
const selectedEpicHasChildren = useMemo(() => {
|
const selectedEpicHasChildren = useMemo(() => {
|
||||||
if (selectedEpic) {
|
if (selectedEpic) {
|
||||||
|
|
@ -326,6 +332,9 @@ export function DependencyGraphPage({
|
||||||
// --- Graph model ---
|
// --- Graph model ---
|
||||||
const graphModel = useMemo(() => buildGraphModel(issues, { projectKey: projectRoot }), [issues, projectRoot]);
|
const graphModel = useMemo(() => buildGraphModel(issues, { projectKey: projectRoot }), [issues, projectRoot]);
|
||||||
|
|
||||||
|
// --- Transitive edges (redundant blocks) ---
|
||||||
|
const transitiveEdges = useMemo(() => identifyTransitiveEdges(graphModel), [graphModel]);
|
||||||
|
|
||||||
// --- Signal map: blocker/blocks counts per issue ---
|
// --- Signal map: blocker/blocks counts per issue ---
|
||||||
const signalById = useMemo(() => {
|
const signalById = useMemo(() => {
|
||||||
const map = new Map<string, { blockedBy: number; blocks: number }>();
|
const map = new Map<string, { blockedBy: number; blocks: number }>();
|
||||||
|
|
@ -531,7 +540,7 @@ export function DependencyGraphPage({
|
||||||
blocks: signalById.get(issue.id)?.blocks ?? 0,
|
blocks: signalById.get(issue.id)?.blocks ?? 0,
|
||||||
isActionable: actionableNodeIds.has(issue.id),
|
isActionable: actionableNodeIds.has(issue.id),
|
||||||
isCycleNode: cycleNodeIdSet.has(issue.id),
|
isCycleNode: cycleNodeIdSet.has(issue.id),
|
||||||
isDimmed: selectedId ? !chainNodeIds.has(issue.id) : false,
|
isDimmed: focusId ? !chainNodeIds.has(issue.id) : false,
|
||||||
blockerTooltipLines: externalBlockerNames.get(issue.id) ?? blockerTooltipMap.get(issue.id) ?? [],
|
blockerTooltipLines: externalBlockerNames.get(issue.id) ?? blockerTooltipMap.get(issue.id) ?? [],
|
||||||
labels: issue.labels,
|
labels: issue.labels,
|
||||||
},
|
},
|
||||||
|
|
@ -542,6 +551,63 @@ export function DependencyGraphPage({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const visibleIds = new Set(baseNodes.map((node) => node.id));
|
const visibleIds = new Set(baseNodes.map((node) => node.id));
|
||||||
|
|
||||||
|
// Use requestedTaskId from URL as the focus node for upstream/downstream highlighting.
|
||||||
|
// `selectedId` is local state that tracks click selection for the drawer,
|
||||||
|
// but it starts as null. The URL `task` param is what the user clicked in the graph
|
||||||
|
// (set by handleNodeSelect -> router.push). We use requestedTaskId here
|
||||||
|
// so that clicking a node - which updates the URL - also triggers edge color changes.
|
||||||
|
const focusId = requestedTaskId;
|
||||||
|
|
||||||
|
// --- Compute Upstream / Downstream Focus ---
|
||||||
|
const upstreamIds = new Set<string>();
|
||||||
|
const downstreamIds = new Set<string>();
|
||||||
|
|
||||||
|
if (focusId && visibleIds.has(focusId)) {
|
||||||
|
upstreamIds.add(focusId);
|
||||||
|
downstreamIds.add(focusId);
|
||||||
|
|
||||||
|
const outgoing = new Map<string, string[]>();
|
||||||
|
const incoming = new Map<string, string[]>();
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
for (const dep of issue.dependencies) {
|
||||||
|
if (dep.type === 'blocks') {
|
||||||
|
const blocker = dep.target;
|
||||||
|
const blocked = issue.id;
|
||||||
|
|
||||||
|
if (!outgoing.has(blocker)) outgoing.set(blocker, []);
|
||||||
|
if (!incoming.has(blocked)) incoming.set(blocked, []);
|
||||||
|
|
||||||
|
outgoing.get(blocker)!.push(blocked);
|
||||||
|
incoming.get(blocked)!.push(blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let queue = [focusId];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const curr = queue.shift()!;
|
||||||
|
for (const b of (incoming.get(curr) || [])) {
|
||||||
|
if (!upstreamIds.has(b)) {
|
||||||
|
upstreamIds.add(b);
|
||||||
|
queue.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue = [focusId];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const curr = queue.shift()!;
|
||||||
|
for (const b of (outgoing.get(curr) || [])) {
|
||||||
|
if (!downstreamIds.has(b)) {
|
||||||
|
downstreamIds.add(b);
|
||||||
|
queue.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const graphEdges: Edge[] = [];
|
const graphEdges: Edge[] = [];
|
||||||
|
|
||||||
// Search ALL issues for blocking edges between visible nodes.
|
// Search ALL issues for blocking edges between visible nodes.
|
||||||
|
|
@ -550,25 +616,87 @@ export function DependencyGraphPage({
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
for (const dep of issue.dependencies) {
|
for (const dep of issue.dependencies) {
|
||||||
// Both endpoints must be visible in the graph
|
// Both endpoints must be visible in the graph
|
||||||
if (!visibleIds.has(issue.id) && !visibleIds.has(dep.target)) continue;
|
|
||||||
if (!visibleIds.has(issue.id) || !visibleIds.has(dep.target)) continue;
|
if (!visibleIds.has(issue.id) || !visibleIds.has(dep.target)) continue;
|
||||||
// Only show blocking edges (skip parent, relates_to, etc.)
|
// Only show blocking edges (skip parent, relates_to, etc.)
|
||||||
if (dep.type !== 'blocks') continue;
|
if (dep.type !== 'blocks') continue;
|
||||||
// Avoid self-loops
|
// Avoid self-loops
|
||||||
if (issue.id === dep.target) continue;
|
if (issue.id === dep.target) continue;
|
||||||
const edgeId = `${dep.target}:blocks:${issue.id}`;
|
|
||||||
|
|
||||||
const linkedToSelection = selectedId ? issue.id === selectedId || dep.target === selectedId : false;
|
const edgeId = `${dep.target}:blocks:${issue.id}`;
|
||||||
|
const sourceId = dep.target;
|
||||||
|
const targetId = issue.id;
|
||||||
|
|
||||||
|
const isUpstreamOfFocus = focusId ? upstreamIds.has(sourceId) && upstreamIds.has(targetId) : false;
|
||||||
|
const isDownstreamOfFocus = focusId ? downstreamIds.has(sourceId) && downstreamIds.has(targetId) : false;
|
||||||
|
const isDirectlyFocused = focusId ? sourceId === focusId || targetId === focusId : false;
|
||||||
|
|
||||||
|
let isUnrelated = false;
|
||||||
|
if (focusId) {
|
||||||
|
isUnrelated = !isUpstreamOfFocus && !isDownstreamOfFocus && !isDirectlyFocused;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceNode = issues.find(i => i.id === sourceId);
|
||||||
|
const sourceStatus = sourceNode?.status || 'open';
|
||||||
|
const isTransitive = transitiveEdges.has(edgeId);
|
||||||
|
|
||||||
|
let stroke = '#3b82f6';
|
||||||
|
let strokeBg = 'rgba(59, 130, 246, 0.25)';
|
||||||
|
let dashArray: string | undefined = undefined;
|
||||||
|
let opacity = 0.8;
|
||||||
|
|
||||||
|
const isFocusedPath = isUpstreamOfFocus || isDownstreamOfFocus || isDirectlyFocused;
|
||||||
|
const isAnimated = isFocusedPath || sourceStatus === 'in_progress';
|
||||||
|
|
||||||
|
// Base Status Colors
|
||||||
|
if (sourceStatus === 'in_progress') {
|
||||||
|
stroke = '#fbbf24'; // Bright Amber
|
||||||
|
strokeBg = 'rgba(251, 191, 36, 0.25)';
|
||||||
|
} else if (sourceStatus === 'blocked') {
|
||||||
|
stroke = '#f43f5e'; // Rose/Red for deep block
|
||||||
|
strokeBg = 'rgba(244, 63, 94, 0.25)';
|
||||||
|
} else {
|
||||||
|
stroke = '#3b82f6'; // Blue for open/ready
|
||||||
|
strokeBg = 'rgba(59, 130, 246, 0.25)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection Focus Overrides
|
||||||
|
if (focusId) {
|
||||||
|
if (isUnrelated) {
|
||||||
|
stroke = '#1e293b'; // Super dim unrelated edges
|
||||||
|
strokeBg = 'transparent';
|
||||||
|
opacity = 0.15;
|
||||||
|
} else if (isUpstreamOfFocus || (isDirectlyFocused && targetId === focusId)) {
|
||||||
|
stroke = '#f59e0b'; // Amber -- "I am blocking you"
|
||||||
|
strokeBg = 'rgba(245, 158, 11, 0.35)';
|
||||||
|
opacity = 1;
|
||||||
|
} else if (isDownstreamOfFocus || (isDirectlyFocused && sourceId === focusId)) {
|
||||||
|
stroke = '#0ea5e9'; // Cyan -- "you are blocking me"
|
||||||
|
strokeBg = 'rgba(14, 165, 233, 0.35)';
|
||||||
|
opacity = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transitive Styling
|
||||||
|
if (isTransitive) {
|
||||||
|
dashArray = '4 4';
|
||||||
|
if (!focusId || isUnrelated) {
|
||||||
|
stroke = '#334155';
|
||||||
|
strokeBg = 'rgba(51, 65, 85, 0.3)';
|
||||||
|
opacity = 0.4;
|
||||||
|
} else {
|
||||||
|
opacity = 0.6; // Keep focused color but make dashed/transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
graphEdges.push({
|
graphEdges.push({
|
||||||
id: edgeId,
|
id: edgeId,
|
||||||
source: dep.target,
|
source: sourceId,
|
||||||
target: issue.id,
|
target: targetId,
|
||||||
className: linkedToSelection ? 'workflow-edge-selected' : 'workflow-edge-muted',
|
className: isFocusedPath ? 'workflow-edge-selected' : 'workflow-edge-muted',
|
||||||
animated: linkedToSelection,
|
animated: isAnimated,
|
||||||
label: 'BLOCKS',
|
label: 'BLOCKS',
|
||||||
labelStyle: {
|
labelStyle: {
|
||||||
fill: linkedToSelection ? '#e2e8f0' : '#cbd5e1',
|
fill: isFocusedPath ? '#e2e8f0' : '#cbd5e1',
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
letterSpacing: '0.08em',
|
letterSpacing: '0.08em',
|
||||||
|
|
@ -577,25 +705,50 @@ export function DependencyGraphPage({
|
||||||
labelBgBorderRadius: 999,
|
labelBgBorderRadius: 999,
|
||||||
labelBgStyle: {
|
labelBgStyle: {
|
||||||
fill: 'rgba(2, 6, 23, 0.92)',
|
fill: 'rgba(2, 6, 23, 0.92)',
|
||||||
stroke: linkedToSelection ? 'rgba(125, 211, 252, 0.35)' : 'rgba(251, 191, 36, 0.25)',
|
stroke: strokeBg,
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
stroke: linkedToSelection ? '#7dd3fc' : '#fbbf24',
|
stroke,
|
||||||
strokeWidth: linkedToSelection ? 2.8 : 2.1,
|
strokeWidth: isFocusedPath ? 2.8 : 2.1,
|
||||||
opacity: linkedToSelection ? 1 : 0.78,
|
opacity,
|
||||||
|
strokeDasharray: dashArray,
|
||||||
},
|
},
|
||||||
markerEnd: { type: MarkerType.ArrowClosed, color: linkedToSelection ? '#7dd3fc' : '#fbbf24', width: 14, height: 14 },
|
markerEnd: { type: MarkerType.ArrowClosed, color: stroke, width: 14, height: 14 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Apply Offsets to Edge Data ---
|
||||||
|
// Count how many edges share the same source and target, or just
|
||||||
|
// group them by axis line to separate them visually.
|
||||||
|
const edgeGroups = new Map<string, Edge[]>();
|
||||||
|
|
||||||
|
for (const edge of graphEdges) {
|
||||||
|
// Create a normalized key roughly defining the segment direction
|
||||||
|
const key = [edge.source, edge.target].sort().join('-');
|
||||||
|
if (!edgeGroups.has(key)) edgeGroups.set(key, []);
|
||||||
|
edgeGroups.get(key)!.push(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign offsets based on index in their shared group.
|
||||||
|
for (const [unused_, groupEdges] of edgeGroups) {
|
||||||
|
if (groupEdges.length <= 1) continue;
|
||||||
|
const step = 8; // 8px offset per line
|
||||||
|
const totalSpread = (groupEdges.length - 1) * step;
|
||||||
|
let currentOffset = -(totalSpread / 2);
|
||||||
|
for (const edge of groupEdges) {
|
||||||
|
edge.data = { ...edge.data, offset: currentOffset };
|
||||||
|
currentOffset += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: layoutDagre(baseNodes, graphEdges),
|
nodes: layoutDagre(baseNodes, graphEdges),
|
||||||
edges: graphEdges,
|
edges: graphEdges,
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
hideClosed, issues, selectedEpicTasks, selectedId,
|
transitiveEdges, hideClosed, issues, selectedEpicTasks, requestedTaskId,
|
||||||
signalById, actionableNodeIds, cycleNodeIdSet,
|
signalById, actionableNodeIds, cycleNodeIdSet,
|
||||||
chainNodeIds, blockerTooltipMap, externalBlockerNames,
|
chainNodeIds, blockerTooltipMap, externalBlockerNames,
|
||||||
]);
|
]);
|
||||||
|
|
@ -607,6 +760,13 @@ export function DependencyGraphPage({
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const edgeTypes = useMemo(
|
||||||
|
() => ({
|
||||||
|
offset: OffsetEdge,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// --- Handle node click in the graph (also opens detail drawer) ---
|
// --- Handle node click in the graph (also opens detail drawer) ---
|
||||||
const handleFlowNodeClick: NodeMouseHandler = useCallback((_, node) => {
|
const handleFlowNodeClick: NodeMouseHandler = useCallback((_, node) => {
|
||||||
setSelectedId(node.id);
|
setSelectedId(node.id);
|
||||||
|
|
@ -714,7 +874,7 @@ export function DependencyGraphPage({
|
||||||
{/* Epic chip strip - shows titles, not just IDs */}
|
{/* Epic chip strip - shows titles, not just IDs */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<EpicChipStrip
|
<EpicChipStrip
|
||||||
epics={epics}
|
epics={selectableEpics}
|
||||||
selectedEpicId={selectedEpicId}
|
selectedEpicId={selectedEpicId}
|
||||||
beadCounts={beadCounts}
|
beadCounts={beadCounts}
|
||||||
onSelect={setSelectedEpicId}
|
onSelect={setSelectedEpicId}
|
||||||
|
|
@ -826,7 +986,7 @@ export function DependencyGraphPage({
|
||||||
<h2 className="text-[10px] font-bold uppercase tracking-[0.2em] text-text-muted/70">1) Select Epic</h2>
|
<h2 className="text-[10px] font-bold uppercase tracking-[0.2em] text-text-muted/70">1) Select Epic</h2>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<EpicChipStrip
|
<EpicChipStrip
|
||||||
epics={epics}
|
epics={selectableEpics}
|
||||||
selectedEpicId={selectedEpicId}
|
selectedEpicId={selectedEpicId}
|
||||||
beadCounts={beadCounts}
|
beadCounts={beadCounts}
|
||||||
onSelect={setSelectedEpicId}
|
onSelect={setSelectedEpicId}
|
||||||
|
|
@ -893,6 +1053,7 @@ export function DependencyGraphPage({
|
||||||
nodes={flowModel.nodes}
|
nodes={flowModel.nodes}
|
||||||
edges={flowModel.edges}
|
edges={flowModel.edges}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
defaultEdgeOptions={defaultEdgeOptions}
|
defaultEdgeOptions={defaultEdgeOptions}
|
||||||
onNodeClick={handleFlowNodeClick}
|
onNodeClick={handleFlowNodeClick}
|
||||||
blockerAnalysis={blockerAnalysis}
|
blockerAnalysis={blockerAnalysis}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ interface GraphSectionProps {
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
/** Map of custom node type names to their React components. */
|
/** Map of custom node type names to their React components. */
|
||||||
nodeTypes: NodeTypes;
|
nodeTypes: NodeTypes;
|
||||||
|
edgeTypes?: any;
|
||||||
/** Default edge rendering options. */
|
/** Default edge rendering options. */
|
||||||
defaultEdgeOptions: {
|
defaultEdgeOptions: {
|
||||||
type: 'smoothstep';
|
type: 'smoothstep';
|
||||||
|
|
@ -43,6 +44,7 @@ export function GraphSection({
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
|
edgeTypes,
|
||||||
defaultEdgeOptions,
|
defaultEdgeOptions,
|
||||||
onNodeClick,
|
onNodeClick,
|
||||||
blockerAnalysis,
|
blockerAnalysis,
|
||||||
|
|
@ -101,6 +103,7 @@ export function GraphSection({
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
nodesDraggable={false}
|
nodesDraggable={false}
|
||||||
nodesConnectable={false}
|
nodesConnectable={false}
|
||||||
elementsSelectable
|
elementsSelectable
|
||||||
|
|
|
||||||
82
src/components/graph/offset-edge.tsx
Normal file
82
src/components/graph/offset-edge.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { BaseEdge, EdgeProps, getSmoothStepPath, EdgeLabelRenderer } from '@xyflow/react';
|
||||||
|
|
||||||
|
export function OffsetEdge(props: EdgeProps) {
|
||||||
|
const {
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
sourcePosition,
|
||||||
|
targetPosition,
|
||||||
|
style = {},
|
||||||
|
markerEnd,
|
||||||
|
data,
|
||||||
|
label,
|
||||||
|
labelStyle,
|
||||||
|
labelBgStyle,
|
||||||
|
labelBgPadding,
|
||||||
|
labelBgBorderRadius,
|
||||||
|
animated,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// We can pass `offset` via the edge data. Positive or negative pixels.
|
||||||
|
const offset = data?.offset as number | undefined ?? 0;
|
||||||
|
|
||||||
|
// Apply offset to the Y axis for Left/Right layouts (horizontal edges)
|
||||||
|
// or to the X axis for Top/Bottom layouts (vertical edges).
|
||||||
|
// Assuming 'sourcePosition' dictates the primary flow direction.
|
||||||
|
let sx = sourceX;
|
||||||
|
let sy = sourceY;
|
||||||
|
let tx = targetX;
|
||||||
|
let ty = targetY;
|
||||||
|
|
||||||
|
if (sourcePosition === 'right' || sourcePosition === 'left') {
|
||||||
|
// Horizontal flow, offset the vertical axis (Y)
|
||||||
|
sy += offset;
|
||||||
|
ty += offset;
|
||||||
|
} else {
|
||||||
|
// Vertical flow, offset the horizontal axis (X)
|
||||||
|
sx += offset;
|
||||||
|
tx += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [edgePath, labelX, labelY] = getSmoothStepPath({
|
||||||
|
sourceX: sx,
|
||||||
|
sourceY: sy,
|
||||||
|
sourcePosition,
|
||||||
|
targetX: tx,
|
||||||
|
targetY: ty,
|
||||||
|
targetPosition,
|
||||||
|
// Optional: reduce the corner radius slightly for tighter clusters
|
||||||
|
borderRadius: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BaseEdge
|
||||||
|
path={edgePath}
|
||||||
|
markerEnd={markerEnd}
|
||||||
|
className={animated ? "animated-edge" : ""}
|
||||||
|
style={{ ...style, strokeDasharray: animated ? "5, 5" : "none" }}
|
||||||
|
/>
|
||||||
|
{label && (
|
||||||
|
<EdgeLabelRenderer>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
||||||
|
pointerEvents: 'all',
|
||||||
|
...(labelBgStyle as React.CSSProperties),
|
||||||
|
padding: Array.isArray(labelBgPadding) ? `${labelBgPadding[0]}px ${labelBgPadding[1]}px` : labelBgPadding,
|
||||||
|
borderRadius: labelBgBorderRadius,
|
||||||
|
}}
|
||||||
|
className="nodrag nopan"
|
||||||
|
>
|
||||||
|
<div style={labelStyle as React.CSSProperties}>{label}</div>
|
||||||
|
</div>
|
||||||
|
</EdgeLabelRenderer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import { useUrlState, buildUrlParams } from '../../hooks/use-url-state';
|
||||||
|
|
||||||
import type { BeadIssue } from '../../lib/types';
|
import type { BeadIssue } from '../../lib/types';
|
||||||
import type { GraphHopDepth } from '../../lib/graph-view';
|
import type { GraphHopDepth } from '../../lib/graph-view';
|
||||||
|
import { collectEpicDescendantIds } from '../../lib/epic-graph';
|
||||||
import { WorkflowGraph } from '../shared/workflow-graph';
|
import { WorkflowGraph } from '../shared/workflow-graph';
|
||||||
import { WorkflowTabs, type WorkflowTab } from './workflow-tabs';
|
import { WorkflowTabs, type WorkflowTab } from './workflow-tabs';
|
||||||
import { TaskCardGrid, type BlockerDetail } from './task-card-grid';
|
import { TaskCardGrid, type BlockerDetail } from './task-card-grid';
|
||||||
|
|
@ -75,11 +76,8 @@ export function SmartDag({
|
||||||
|
|
||||||
const displayBeads = useMemo(() => {
|
const displayBeads = useMemo(() => {
|
||||||
if (!epicId) return issues;
|
if (!epicId) return issues;
|
||||||
return issues.filter(issue => {
|
const descendantIds = collectEpicDescendantIds(issues, epicId);
|
||||||
if (issue.issue_type === 'epic') return false;
|
return issues.filter((issue) => descendantIds.has(issue.id));
|
||||||
const parent = issue.dependencies.find(d => d.type === 'parent');
|
|
||||||
return parent?.target === epicId;
|
|
||||||
});
|
|
||||||
}, [issues, epicId]);
|
}, [issues, epicId]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,32 @@ interface EpicEntry {
|
||||||
latestTimestamp: string;
|
latestTimestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldHideEpicEntry(params: {
|
||||||
|
epicStatus: BeadIssue['status'];
|
||||||
|
matchedChildrenCount: number;
|
||||||
|
totalChildrenCount: number;
|
||||||
|
isSelected: boolean;
|
||||||
|
filters: LeftPanelFilters;
|
||||||
|
}): boolean {
|
||||||
|
const { epicStatus, matchedChildrenCount, totalChildrenCount, isSelected, filters } = params;
|
||||||
|
const hasTaskFilters =
|
||||||
|
filters.query.trim().length > 0 ||
|
||||||
|
filters.status !== 'all' ||
|
||||||
|
filters.priority !== 'all' ||
|
||||||
|
filters.preset !== 'all';
|
||||||
|
const epicClosed = epicStatus === 'closed' || epicStatus === 'tombstone';
|
||||||
|
const noVisibleChildren = matchedChildrenCount === 0 && totalChildrenCount > 0;
|
||||||
|
const hiddenByTaskFilters = hasTaskFilters && noVisibleChildren;
|
||||||
|
const hiddenByHideClosed = filters.hideClosed && noVisibleChildren;
|
||||||
|
const hiddenByEpicClosed = filters.hideClosed && epicClosed;
|
||||||
|
|
||||||
|
if (hiddenByEpicClosed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isSelected && (hiddenByTaskFilters || hiddenByHideClosed);
|
||||||
|
}
|
||||||
|
|
||||||
function mapStatus(task: BeadIssue): LeftPanelStatusFilter {
|
function mapStatus(task: BeadIssue): LeftPanelStatusFilter {
|
||||||
if (task.status === 'open') return 'ready';
|
if (task.status === 'open') return 'ready';
|
||||||
if (task.status === 'in_progress') return 'in_progress';
|
if (task.status === 'in_progress') return 'in_progress';
|
||||||
|
|
@ -179,13 +205,6 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, onEpicEdit, fi
|
||||||
const entries = useMemo(() => buildEntries(issues), [issues]);
|
const entries = useMemo(() => buildEntries(issues), [issues]);
|
||||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
const hasActiveFilters =
|
|
||||||
filters.query.trim().length > 0 ||
|
|
||||||
filters.status !== 'all' ||
|
|
||||||
filters.priority !== 'all' ||
|
|
||||||
filters.preset !== 'all' ||
|
|
||||||
filters.hideClosed;
|
|
||||||
|
|
||||||
const views: Array<{ id: ViewType; label: string }> = [
|
const views: Array<{ id: ViewType; label: string }> = [
|
||||||
{ id: 'social', label: 'Social' },
|
{ id: 'social', label: 'Social' },
|
||||||
{ id: 'graph', label: 'Graph' },
|
{ id: 'graph', label: 'Graph' },
|
||||||
|
|
@ -324,7 +343,13 @@ export function LeftPanel({ issues, selectedEpicId, onEpicSelect, onEpicEdit, fi
|
||||||
const laneColor = blockedCount > 0 ? 'var(--accent-danger)' : activeCount > 0 ? 'var(--accent-warning)' : 'var(--accent-success)';
|
const laneColor = blockedCount > 0 ? 'var(--accent-danger)' : activeCount > 0 ? 'var(--accent-warning)' : 'var(--accent-success)';
|
||||||
const rowBackground = rowTone(entry);
|
const rowBackground = rowTone(entry);
|
||||||
|
|
||||||
if (matchedChildren.length === 0 && hasActiveFilters && !isSelected) {
|
if (shouldHideEpicEntry({
|
||||||
|
epicStatus: epic.status,
|
||||||
|
matchedChildrenCount: matchedChildren.length,
|
||||||
|
totalChildrenCount: total,
|
||||||
|
isSelected,
|
||||||
|
filters,
|
||||||
|
})) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,19 @@ export function UnifiedShell({
|
||||||
const drawerId = taskId || swarmId || epicId || '';
|
const drawerId = taskId || swarmId || epicId || '';
|
||||||
const selectedItem = selectedEpic ?? selectedIssue;
|
const selectedItem = selectedEpic ?? selectedIssue;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!filters.hideClosed || !epicId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const epic = issues.find((issue) => issue.id === epicId && issue.issue_type === 'epic');
|
||||||
|
if (!epic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (epic.status === 'closed' || epic.status === 'tombstone') {
|
||||||
|
setEpicId(null);
|
||||||
|
}
|
||||||
|
}, [filters.hideClosed, epicId, issues, setEpicId]);
|
||||||
|
|
||||||
// Panel resize hook
|
// Panel resize hook
|
||||||
const { leftWidth, rightWidth, handleLeftResize, handleRightResize } = usePanelResize();
|
const { leftWidth, rightWidth, handleLeftResize, handleRightResize } = usePanelResize();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,11 @@ import { Maximize2 } from 'lucide-react';
|
||||||
|
|
||||||
import type { BeadIssue } from '../../lib/types';
|
import type { BeadIssue } from '../../lib/types';
|
||||||
import type { AgentArchetype } from '../../lib/types-swarm';
|
import type { AgentArchetype } from '../../lib/types-swarm';
|
||||||
|
import { buildWorkflowEdges } from '../../lib/epic-graph';
|
||||||
import { useGraphAnalysis } from '../../hooks/use-graph-analysis';
|
import { useGraphAnalysis } from '../../hooks/use-graph-analysis';
|
||||||
|
import { identifyTransitiveEdges } from '../../lib/graph-view';
|
||||||
import { GraphNodeCard, type GraphNodeData } from '../graph/graph-node-card';
|
import { GraphNodeCard, type GraphNodeData } from '../graph/graph-node-card';
|
||||||
|
import { OffsetEdge } from '../graph/offset-edge';
|
||||||
|
|
||||||
export interface WorkflowGraphProps {
|
export interface WorkflowGraphProps {
|
||||||
beads: BeadIssue[];
|
beads: BeadIssue[];
|
||||||
|
|
@ -92,9 +95,11 @@ function WorkflowGraphInner({
|
||||||
const { fitView } = useReactFlow();
|
const { fitView } = useReactFlow();
|
||||||
const [layoutDirection, setLayoutDirection] = useState<LayoutDirection>('LR');
|
const [layoutDirection, setLayoutDirection] = useState<LayoutDirection>('LR');
|
||||||
const [layoutDensity, setLayoutDensity] = useState<LayoutDensity>('normal');
|
const [layoutDensity, setLayoutDensity] = useState<LayoutDensity>('normal');
|
||||||
|
const [showHierarchy, setShowHierarchy] = useState(true);
|
||||||
|
|
||||||
// Use the extracted hook for all graph analysis
|
// Use the extracted hook for all graph analysis
|
||||||
const {
|
const {
|
||||||
|
graphModel,
|
||||||
signalById,
|
signalById,
|
||||||
cycleNodeIdSet,
|
cycleNodeIdSet,
|
||||||
actionableNodeIds,
|
actionableNodeIds,
|
||||||
|
|
@ -103,6 +108,8 @@ function WorkflowGraphInner({
|
||||||
chainNodeIds,
|
chainNodeIds,
|
||||||
} = useGraphAnalysis(beads, 'workflow', selectedId);
|
} = useGraphAnalysis(beads, 'workflow', selectedId);
|
||||||
|
|
||||||
|
const transitiveEdges = useMemo(() => identifyTransitiveEdges(graphModel), [graphModel]);
|
||||||
|
|
||||||
const flowModel = useMemo(() => {
|
const flowModel = useMemo(() => {
|
||||||
const visibleBeads = beads.filter((issue) => (!hideClosed ? true : issue.status !== 'closed'));
|
const visibleBeads = beads.filter((issue) => (!hideClosed ? true : issue.status !== 'closed'));
|
||||||
|
|
||||||
|
|
@ -155,52 +162,128 @@ function WorkflowGraphInner({
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibleIds = new Set(baseNodes.map((node) => node.id));
|
const visibleIds = new Set(baseNodes.map((node) => node.id));
|
||||||
const graphEdges: Edge[] = [];
|
const edgeDescriptors = buildWorkflowEdges({
|
||||||
|
issues: beads,
|
||||||
|
visibleIds,
|
||||||
|
selectedId: selectedId ?? null,
|
||||||
|
includeHierarchy: showHierarchy,
|
||||||
|
});
|
||||||
|
|
||||||
for (const issue of beads) {
|
const graphEdges: Edge[] = edgeDescriptors.map((edge) => {
|
||||||
for (const dep of issue.dependencies) {
|
const isSubtask = edge.kind === 'subtask';
|
||||||
if (!visibleIds.has(issue.id) && !visibleIds.has(dep.target)) continue;
|
const label = isSubtask ? 'SUBTASK' : 'BLOCKS';
|
||||||
if (!visibleIds.has(issue.id) || !visibleIds.has(dep.target)) continue;
|
const isTransitive = transitiveEdges.has(`${edge.source}:blocks:${edge.target}`);
|
||||||
if (dep.type !== 'blocks') continue;
|
|
||||||
if (issue.id === dep.target) continue;
|
|
||||||
|
|
||||||
const edgeId = `${dep.target}:blocks:${issue.id}`;
|
let stroke = '#64748b'; // default slate for subtasks / generic
|
||||||
const linkedToSelection = selectedId ? issue.id === selectedId || dep.target === selectedId : false;
|
let strokeBg = 'rgba(100, 116, 139, 0.3)';
|
||||||
const sourceIssue = beads.find((i) => i.id === dep.target);
|
let dashArray: string | undefined = undefined;
|
||||||
const isInProgressEdge = issue.status === 'in_progress' || sourceIssue?.status === 'in_progress';
|
let opacity = 0.78;
|
||||||
|
|
||||||
graphEdges.push({
|
const isFocusedPath = edge.isUpstreamOfFocus || edge.isDownstreamOfFocus || edge.isDirectlyFocused;
|
||||||
id: edgeId,
|
const isAnimated = isFocusedPath || edge.sourceStatus === 'in_progress';
|
||||||
source: dep.target,
|
|
||||||
target: issue.id,
|
if (isSubtask) {
|
||||||
className: linkedToSelection ? 'workflow-edge-selected' : 'workflow-edge-muted',
|
stroke = isFocusedPath ? '#94a3b8' : '#64748b';
|
||||||
animated: linkedToSelection || isInProgressEdge,
|
strokeBg = isFocusedPath ? 'rgba(148, 163, 184, 0.4)' : 'rgba(100, 116, 139, 0.3)';
|
||||||
label: 'BLOCKS',
|
dashArray = '6 4';
|
||||||
labelStyle: {
|
opacity = isFocusedPath ? 1 : (edge.isUnrelated ? 0.15 : 0.58);
|
||||||
fill: linkedToSelection ? '#e2e8f0' : '#cbd5e1',
|
} else {
|
||||||
fontSize: 10,
|
// Evaluate Base Status
|
||||||
fontWeight: 700,
|
if (edge.sourceStatus === 'in_progress') {
|
||||||
letterSpacing: '0.08em',
|
stroke = '#fbbf24'; // Bright Amber
|
||||||
},
|
strokeBg = 'rgba(251, 191, 36, 0.25)';
|
||||||
labelBgPadding: [6, 3],
|
} else if (edge.sourceStatus === 'blocked') {
|
||||||
labelBgBorderRadius: 999,
|
stroke = '#f43f5e'; // Rose/Red for deep block
|
||||||
labelBgStyle: {
|
strokeBg = 'rgba(244, 63, 94, 0.25)';
|
||||||
fill: 'rgba(2, 6, 23, 0.92)',
|
} else {
|
||||||
stroke: linkedToSelection ? 'rgba(125, 211, 252, 0.35)' : 'rgba(251, 191, 36, 0.25)',
|
stroke = '#3b82f6'; // Blue
|
||||||
strokeWidth: 1,
|
strokeBg = 'rgba(59, 130, 246, 0.25)';
|
||||||
},
|
}
|
||||||
style: {
|
|
||||||
stroke: linkedToSelection ? '#7dd3fc' : '#fbbf24',
|
// Overrides for Selection
|
||||||
strokeWidth: linkedToSelection ? 2.8 : 2.1,
|
if (selectedId) {
|
||||||
opacity: linkedToSelection ? 1 : 0.78,
|
if (edge.isUnrelated) {
|
||||||
},
|
stroke = '#1e293b'; // Super dim
|
||||||
markerEnd: {
|
strokeBg = 'transparent';
|
||||||
type: MarkerType.ArrowClosed,
|
opacity = 0.15;
|
||||||
color: linkedToSelection ? '#7dd3fc' : '#fbbf24',
|
} else if (edge.isUpstreamOfFocus || (edge.isDirectlyFocused && edge.target === selectedId)) {
|
||||||
width: 14,
|
stroke = '#f59e0b'; // Amber for upstream blockers
|
||||||
height: 14,
|
strokeBg = 'rgba(245, 158, 11, 0.35)';
|
||||||
},
|
opacity = 1;
|
||||||
});
|
} else if (edge.isDownstreamOfFocus || (edge.isDirectlyFocused && edge.source === selectedId)) {
|
||||||
|
stroke = '#0ea5e9'; // Cyan for downstream impact
|
||||||
|
strokeBg = 'rgba(14, 165, 233, 0.35)';
|
||||||
|
opacity = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transitive styling
|
||||||
|
if (isTransitive) {
|
||||||
|
dashArray = '4 4';
|
||||||
|
if (!selectedId || edge.isUnrelated) {
|
||||||
|
stroke = '#334155';
|
||||||
|
strokeBg = 'rgba(51, 65, 85, 0.3)';
|
||||||
|
opacity = 0.4;
|
||||||
|
} else {
|
||||||
|
opacity = 0.6; // Keep the focused color but make it dashed & slightly transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.source,
|
||||||
|
target: edge.target,
|
||||||
|
className: isFocusedPath ? 'workflow-edge-selected' : 'workflow-edge-muted',
|
||||||
|
animated: isAnimated,
|
||||||
|
label,
|
||||||
|
labelStyle: {
|
||||||
|
fill: isFocusedPath ? '#e2e8f0' : '#cbd5e1',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.08em',
|
||||||
|
},
|
||||||
|
labelBgPadding: [6, 3],
|
||||||
|
labelBgBorderRadius: 999,
|
||||||
|
labelBgStyle: {
|
||||||
|
fill: 'rgba(2, 6, 23, 0.92)',
|
||||||
|
stroke: strokeBg,
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
stroke,
|
||||||
|
strokeWidth: isFocusedPath ? 2.8 : 2.1,
|
||||||
|
opacity,
|
||||||
|
strokeDasharray: dashArray,
|
||||||
|
},
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: stroke,
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Apply Offsets to Edge Data ---
|
||||||
|
const edgeGroups = new Map<string, Edge[]>();
|
||||||
|
|
||||||
|
for (const edge of graphEdges) {
|
||||||
|
const key = [edge.source, edge.target].sort().join('-');
|
||||||
|
if (!edgeGroups.has(key)) edgeGroups.set(key, []);
|
||||||
|
edgeGroups.get(key)!.push(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [unused_, groupEdges] of edgeGroups) {
|
||||||
|
if (groupEdges.length <= 1) continue;
|
||||||
|
// In Vertical layout, we might want X offset, in Horizontal Y offset.
|
||||||
|
// OffsetEdge component already handles adjusting the correct axis based on sourcePosition.
|
||||||
|
const step = 8;
|
||||||
|
const totalSpread = (groupEdges.length - 1) * step;
|
||||||
|
let currentOffset = -(totalSpread / 2);
|
||||||
|
for (const edge of groupEdges) {
|
||||||
|
edge.data = { ...edge.data, offset: currentOffset };
|
||||||
|
currentOffset += step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +291,7 @@ function WorkflowGraphInner({
|
||||||
nodes: layoutDagre(baseNodes, graphEdges, layoutDirection, layoutDensity),
|
nodes: layoutDagre(baseNodes, graphEdges, layoutDirection, layoutDensity),
|
||||||
edges: graphEdges,
|
edges: graphEdges,
|
||||||
};
|
};
|
||||||
}, [beads, hideClosed, selectedId, signalById, actionableNodeIds, cycleNodeIdSet, chainNodeIds, blockerTooltipMap, archetypes, assignMode, onSelect, onViewInSocial, onAssignMode, onViewTelemetry, layoutDirection, layoutDensity]);
|
}, [transitiveEdges, beads, hideClosed, selectedId, signalById, actionableNodeIds, cycleNodeIdSet, chainNodeIds, blockerTooltipMap, archetypes, assignMode, onSelect, onViewInSocial, onAssignMode, onViewTelemetry, layoutDirection, layoutDensity, showHierarchy]);
|
||||||
|
|
||||||
const nodeTypes: NodeTypes = useMemo(
|
const nodeTypes: NodeTypes = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
@ -217,9 +300,16 @@ function WorkflowGraphInner({
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const edgeTypes = useMemo(
|
||||||
|
() => ({
|
||||||
|
offset: OffsetEdge,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const defaultEdgeOptions = useMemo(
|
const defaultEdgeOptions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
type: 'smoothstep' as const,
|
type: 'offset' as const,
|
||||||
zIndex: 40,
|
zIndex: 40,
|
||||||
interactionWidth: 24,
|
interactionWidth: 24,
|
||||||
}),
|
}),
|
||||||
|
|
@ -269,25 +359,34 @@ function WorkflowGraphInner({
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-3 top-3 z-10 flex flex-wrap items-center gap-2">
|
<div className="absolute right-3 top-3 z-10 flex flex-wrap items-center gap-2">
|
||||||
<div className="inline-flex items-center gap-1 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] p-1">
|
<div className="inline-flex items-center gap-1 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] p-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowHierarchy((current) => !current)}
|
||||||
|
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${showHierarchy
|
||||||
|
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
||||||
|
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
||||||
|
}`}
|
||||||
|
title="Show parent/subtask links"
|
||||||
|
>
|
||||||
|
Hierarchy
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLayoutDirection('LR')}
|
onClick={() => setLayoutDirection('LR')}
|
||||||
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${
|
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${layoutDirection === 'LR'
|
||||||
layoutDirection === 'LR'
|
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
||||||
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
||||||
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Horizontal
|
Horizontal
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLayoutDirection('TB')}
|
onClick={() => setLayoutDirection('TB')}
|
||||||
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${
|
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${layoutDirection === 'TB'
|
||||||
layoutDirection === 'TB'
|
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
||||||
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
||||||
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Vertical
|
Vertical
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -296,22 +395,20 @@ function WorkflowGraphInner({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLayoutDensity('compact')}
|
onClick={() => setLayoutDensity('compact')}
|
||||||
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${
|
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${layoutDensity === 'compact'
|
||||||
layoutDensity === 'compact'
|
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
||||||
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
||||||
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Compact
|
Compact
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLayoutDensity('normal')}
|
onClick={() => setLayoutDensity('normal')}
|
||||||
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${
|
className={`rounded-md px-2 py-1 text-[11px] font-semibold transition-colors ${layoutDensity === 'normal'
|
||||||
layoutDensity === 'normal'
|
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
||||||
? 'bg-[var(--surface-hover)] text-[var(--text-primary)]'
|
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
||||||
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Normal
|
Normal
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -339,6 +436,7 @@ function WorkflowGraphInner({
|
||||||
nodes={flowModel.nodes}
|
nodes={flowModel.nodes}
|
||||||
edges={flowModel.edges}
|
edges={flowModel.edges}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
nodesDraggable={false}
|
nodesDraggable={false}
|
||||||
nodesConnectable={false}
|
nodesConnectable={false}
|
||||||
elementsSelectable
|
elementsSelectable
|
||||||
|
|
@ -348,7 +446,7 @@ function WorkflowGraphInner({
|
||||||
<Background gap={32} size={1} color="rgba(255,255,255,0.03)" />
|
<Background gap={32} size={1} color="rgba(255,255,255,0.03)" />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WorkflowGraph(props: WorkflowGraphProps) {
|
export function WorkflowGraph(props: WorkflowGraphProps) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import dagre from 'dagre';
|
||||||
|
|
||||||
import type { BeadIssue } from '../../lib/types';
|
import type { BeadIssue } from '../../lib/types';
|
||||||
import type { AgentArchetype } from '../../lib/types-swarm';
|
import type { AgentArchetype } from '../../lib/types-swarm';
|
||||||
|
import { buildGraphModel } from '../../lib/graph';
|
||||||
|
import { identifyTransitiveEdges } from '../../lib/graph-view';
|
||||||
|
|
||||||
// Custom Node for the Agent DAG
|
// Custom Node for the Agent DAG
|
||||||
interface AgentNodeData extends Record<string, unknown> {
|
interface AgentNodeData extends Record<string, unknown> {
|
||||||
|
|
@ -152,19 +154,45 @@ function SpecializedAgentDagInner({ beads, archetypes, selectedId, onSelect }: {
|
||||||
const graphEdges: Edge[] = [];
|
const graphEdges: Edge[] = [];
|
||||||
const beadIds = new Set(visibleBeads.map(b => b.id));
|
const beadIds = new Set(visibleBeads.map(b => b.id));
|
||||||
|
|
||||||
|
const graphModel = buildGraphModel(visibleBeads);
|
||||||
|
const transitiveEdges = identifyTransitiveEdges(graphModel);
|
||||||
|
|
||||||
visibleBeads.forEach(issue => {
|
visibleBeads.forEach(issue => {
|
||||||
issue.dependencies.forEach(dep => {
|
issue.dependencies.forEach(dep => {
|
||||||
if (dep.type === 'blocks' && beadIds.has(dep.target)) {
|
if (dep.type === 'blocks' && beadIds.has(dep.target)) {
|
||||||
// issue depends on dep.target (issue is blocked by dep.target)
|
// issue depends on dep.target (issue is blocked by dep.target)
|
||||||
// Edge should flow from blocker to blocked
|
// Edge should flow from blocker to blocked
|
||||||
|
const sourceNode = visibleBeads.find(i => i.id === dep.target);
|
||||||
|
const isActiveBlocker = sourceNode?.status === 'in_progress' || sourceNode?.status === 'blocked';
|
||||||
|
const edgeId = `${dep.target}:blocks:${issue.id}`;
|
||||||
|
const isTransitive = transitiveEdges.has(edgeId);
|
||||||
|
|
||||||
|
const isLinked = issue.id === selectedId || dep.target === selectedId;
|
||||||
|
|
||||||
|
let stroke = '#475569';
|
||||||
|
let dashArray: string | undefined = undefined;
|
||||||
|
let opacity = 0.8;
|
||||||
|
|
||||||
|
if (isTransitive && !isLinked) {
|
||||||
|
stroke = '#334155';
|
||||||
|
dashArray = '4 4';
|
||||||
|
opacity = 0.4;
|
||||||
|
} else if (isActiveBlocker) {
|
||||||
|
stroke = isLinked ? '#7dd3fc' : '#fbbf24';
|
||||||
|
opacity = 1;
|
||||||
|
} else {
|
||||||
|
stroke = isLinked ? '#7dd3fc' : '#475569';
|
||||||
|
opacity = isLinked ? 1 : 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
graphEdges.push({
|
graphEdges.push({
|
||||||
id: `e-${dep.target}-${issue.id}`,
|
id: `e-${dep.target}-${issue.id}`,
|
||||||
source: dep.target,
|
source: dep.target,
|
||||||
target: issue.id,
|
target: issue.id,
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: issue.status === 'in_progress' || issue.status === 'closed',
|
animated: isLinked || isActiveBlocker,
|
||||||
style: { stroke: '#475569', strokeWidth: 2 },
|
style: { stroke, strokeWidth: isLinked ? 2.8 : 2, opacity, strokeDasharray: dashArray },
|
||||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#475569' }
|
markerEnd: { type: MarkerType.ArrowClosed, color: stroke }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ export function useBeadsSubscription(
|
||||||
}
|
}
|
||||||
}, [projectRoot, onUpdate]);
|
}, [projectRoot, onUpdate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void refresh({ silent: true });
|
||||||
|
}, [refresh]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const source = new EventSource(`/api/events?projectRoot=${encodeURIComponent(projectRoot)}`);
|
const source = new EventSource(`/api/events?projectRoot=${encodeURIComponent(projectRoot)}`);
|
||||||
|
|
||||||
|
|
|
||||||
200
src/lib/epic-graph.ts
Normal file
200
src/lib/epic-graph.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
import type { BeadIssue } from './types';
|
||||||
|
|
||||||
|
export interface WorkflowEdgeDescriptor {
|
||||||
|
id: string;
|
||||||
|
source: string;
|
||||||
|
target: string;
|
||||||
|
kind: 'blocks' | 'subtask';
|
||||||
|
isUpstreamOfFocus: boolean;
|
||||||
|
isDownstreamOfFocus: boolean;
|
||||||
|
isDirectlyFocused: boolean;
|
||||||
|
isUnrelated: boolean;
|
||||||
|
sourceStatus: string;
|
||||||
|
targetStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BuildWorkflowEdgesOptions {
|
||||||
|
issues: BeadIssue[];
|
||||||
|
visibleIds: Set<string>;
|
||||||
|
selectedId: string | null;
|
||||||
|
includeHierarchy: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectEpicDescendantIds(issues: BeadIssue[], epicId: string): Set<string> {
|
||||||
|
const childrenByParent = new Map<string, string[]>();
|
||||||
|
const issueById = new Map(issues.map((issue) => [issue.id, issue]));
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
for (const dep of issue.dependencies) {
|
||||||
|
if (dep.type !== 'parent') continue;
|
||||||
|
const list = childrenByParent.get(dep.target) ?? [];
|
||||||
|
list.push(issue.id);
|
||||||
|
childrenByParent.set(dep.target, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const descendants = new Set<string>();
|
||||||
|
const queue = [...(childrenByParent.get(epicId) ?? [])];
|
||||||
|
const seen = new Set<string>([epicId]);
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const id = queue.shift();
|
||||||
|
if (!id || seen.has(id)) continue;
|
||||||
|
seen.add(id);
|
||||||
|
|
||||||
|
const issue = issueById.get(id);
|
||||||
|
if (issue && issue.issue_type !== 'epic') {
|
||||||
|
descendants.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = childrenByParent.get(id) ?? [];
|
||||||
|
for (const child of children) {
|
||||||
|
if (!seen.has(child)) {
|
||||||
|
queue.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descendants;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWorkflowEdges({
|
||||||
|
issues,
|
||||||
|
visibleIds,
|
||||||
|
selectedId,
|
||||||
|
includeHierarchy,
|
||||||
|
}: BuildWorkflowEdgesOptions): WorkflowEdgeDescriptor[] {
|
||||||
|
const issueById = new Map(issues.map((issue) => [issue.id, issue]));
|
||||||
|
const edgeIds = new Set<string>();
|
||||||
|
const edges: WorkflowEdgeDescriptor[] = [];
|
||||||
|
|
||||||
|
const upstreamIds = new Set<string>();
|
||||||
|
const downstreamIds = new Set<string>();
|
||||||
|
|
||||||
|
if (selectedId) {
|
||||||
|
upstreamIds.add(selectedId);
|
||||||
|
downstreamIds.add(selectedId);
|
||||||
|
|
||||||
|
// Build adjacency just for tracing blockers
|
||||||
|
const outgoing = new Map<string, string[]>(); // id -> nodes it blocks
|
||||||
|
const incoming = new Map<string, string[]>(); // id -> nodes blocking it
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
for (const dep of issue.dependencies) {
|
||||||
|
if (dep.type === 'blocks') {
|
||||||
|
const blocker = dep.target;
|
||||||
|
const blocked = issue.id;
|
||||||
|
|
||||||
|
if (!outgoing.has(blocker)) outgoing.set(blocker, []);
|
||||||
|
if (!incoming.has(blocked)) incoming.set(blocked, []);
|
||||||
|
|
||||||
|
outgoing.get(blocker)!.push(blocked);
|
||||||
|
incoming.get(blocked)!.push(blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace incoming (upstream blockers)
|
||||||
|
let queue = [selectedId];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const curr = queue.shift()!;
|
||||||
|
for (const b of (incoming.get(curr) || [])) {
|
||||||
|
if (!upstreamIds.has(b)) {
|
||||||
|
upstreamIds.add(b);
|
||||||
|
queue.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace outgoing (downstream blocked)
|
||||||
|
queue = [selectedId];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const curr = queue.shift()!;
|
||||||
|
for (const b of (outgoing.get(curr) || [])) {
|
||||||
|
if (!downstreamIds.has(b)) {
|
||||||
|
downstreamIds.add(b);
|
||||||
|
queue.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
if (!visibleIds.has(issue.id)) continue;
|
||||||
|
|
||||||
|
for (const dep of issue.dependencies) {
|
||||||
|
if (!visibleIds.has(dep.target)) continue;
|
||||||
|
if (dep.target === issue.id) continue;
|
||||||
|
|
||||||
|
if (dep.type === 'blocks') {
|
||||||
|
const source = dep.target;
|
||||||
|
const target = issue.id;
|
||||||
|
const id = `${source}:blocks:${target}`;
|
||||||
|
if (edgeIds.has(id)) continue;
|
||||||
|
edgeIds.add(id);
|
||||||
|
|
||||||
|
const sourceIssue = issueById.get(source);
|
||||||
|
const sourceStatus = sourceIssue?.status || 'open';
|
||||||
|
const targetStatus = issue.status;
|
||||||
|
|
||||||
|
const isUpstreamOfFocus = selectedId ? upstreamIds.has(source) && upstreamIds.has(target) : false;
|
||||||
|
const isDownstreamOfFocus = selectedId ? downstreamIds.has(source) && downstreamIds.has(target) : false;
|
||||||
|
const isDirectlyFocused = selectedId ? source === selectedId || target === selectedId : false;
|
||||||
|
|
||||||
|
let isUnrelated = false;
|
||||||
|
if (selectedId) {
|
||||||
|
isUnrelated = !isUpstreamOfFocus && !isDownstreamOfFocus && !isDirectlyFocused;
|
||||||
|
}
|
||||||
|
|
||||||
|
edges.push({
|
||||||
|
id,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
kind: 'blocks',
|
||||||
|
isUpstreamOfFocus,
|
||||||
|
isDownstreamOfFocus,
|
||||||
|
isDirectlyFocused,
|
||||||
|
isUnrelated,
|
||||||
|
sourceStatus,
|
||||||
|
targetStatus
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeHierarchy && dep.type === 'parent') {
|
||||||
|
const source = dep.target;
|
||||||
|
const target = issue.id;
|
||||||
|
const id = `${source}:subtask:${target}`;
|
||||||
|
if (edgeIds.has(id)) continue;
|
||||||
|
edgeIds.add(id);
|
||||||
|
|
||||||
|
const sourceIssue = issueById.get(source);
|
||||||
|
const sourceStatus = sourceIssue?.status || 'open';
|
||||||
|
const targetStatus = issue.status;
|
||||||
|
|
||||||
|
const isUpstreamOfFocus = selectedId ? upstreamIds.has(source) && upstreamIds.has(target) : false;
|
||||||
|
const isDownstreamOfFocus = selectedId ? downstreamIds.has(source) && downstreamIds.has(target) : false;
|
||||||
|
const isDirectlyFocused = selectedId ? source === selectedId || target === selectedId : false;
|
||||||
|
|
||||||
|
let isUnrelated = false;
|
||||||
|
if (selectedId) {
|
||||||
|
isUnrelated = !isUpstreamOfFocus && !isDownstreamOfFocus && !isDirectlyFocused;
|
||||||
|
}
|
||||||
|
|
||||||
|
edges.push({
|
||||||
|
id,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
kind: 'subtask',
|
||||||
|
isUpstreamOfFocus,
|
||||||
|
isDownstreamOfFocus,
|
||||||
|
isDirectlyFocused,
|
||||||
|
isUnrelated,
|
||||||
|
sourceStatus,
|
||||||
|
targetStatus
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
@ -419,6 +419,60 @@ export function analyzeBlockedChain(model: GraphModel, options: { focusId: strin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies edges in the graph that are transitive (redundant).
|
||||||
|
* For example, if A -> B -> C exists, the edge A -> C is transitive.
|
||||||
|
* This is primarily used to visually de-emphasize redundant blocks in the DAG.
|
||||||
|
* Returns a Set of edge keys formatted as `${source}:blocks:${target}`.
|
||||||
|
*/
|
||||||
|
export function identifyTransitiveEdges(model: GraphModel): Set<string> {
|
||||||
|
const transitiveEdgeKeys = new Set<string>();
|
||||||
|
|
||||||
|
for (const edge of model.edges) {
|
||||||
|
if (edge.type !== 'blocks') continue;
|
||||||
|
|
||||||
|
const sourceId = edge.source;
|
||||||
|
const targetId = edge.target;
|
||||||
|
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const queue = [sourceId];
|
||||||
|
visited.add(sourceId);
|
||||||
|
|
||||||
|
let foundAlternativePath = false;
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const currentId = queue.shift()!;
|
||||||
|
if (currentId === targetId && currentId !== sourceId) {
|
||||||
|
foundAlternativePath = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjacency = model.adjacency[currentId];
|
||||||
|
if (!adjacency) continue;
|
||||||
|
|
||||||
|
for (const outEdge of adjacency.outgoing) {
|
||||||
|
if (outEdge.type !== 'blocks') continue;
|
||||||
|
|
||||||
|
// Skip the immediate direct edge we are evaluating
|
||||||
|
if (currentId === sourceId && outEdge.target === targetId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visited.has(outEdge.target)) {
|
||||||
|
visited.add(outEdge.target);
|
||||||
|
queue.push(outEdge.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAlternativePath) {
|
||||||
|
transitiveEdgeKeys.add(`${sourceId}:blocks:${targetId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transitiveEdgeKeys;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CycleAnomaly {
|
export interface CycleAnomaly {
|
||||||
cycles: string[][];
|
cycles: string[][];
|
||||||
cycleNodeIds: string[];
|
cycleNodeIds: string[];
|
||||||
|
|
|
||||||
103
src/lib/install-manifest.ts
Normal file
103
src/lib/install-manifest.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
export const INSTALLER_SCHEMA_VERSION = 'installer.v1' as const;
|
||||||
|
export const DRIVER_REMEDIATION_MODE = 'detect_only' as const;
|
||||||
|
|
||||||
|
export interface InstallerManifest {
|
||||||
|
version: typeof INSTALLER_SCHEMA_VERSION;
|
||||||
|
distribution: {
|
||||||
|
packageName: string;
|
||||||
|
shimNames: string[];
|
||||||
|
};
|
||||||
|
wrappers: {
|
||||||
|
windows: {
|
||||||
|
script: string;
|
||||||
|
};
|
||||||
|
posix: {
|
||||||
|
script: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
runtime: {
|
||||||
|
start: string;
|
||||||
|
open: string;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
driver: {
|
||||||
|
remediationMode: typeof DRIVER_REMEDIATION_MODE;
|
||||||
|
installSideEffects: false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstallerManifestValidationResult =
|
||||||
|
| { ok: true; value: InstallerManifest }
|
||||||
|
| { ok: false; error: string };
|
||||||
|
|
||||||
|
function fail(error: string): InstallerManifestValidationResult {
|
||||||
|
return { ok: false, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(input: unknown): input is Record<string, unknown> {
|
||||||
|
return typeof input === 'object' && input !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nonEmptyString(input: unknown): input is string {
|
||||||
|
return typeof input === 'string' && input.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateInstallerManifest(input: unknown): InstallerManifestValidationResult {
|
||||||
|
if (!isObject(input)) return fail('manifest must be an object');
|
||||||
|
if (input.version !== INSTALLER_SCHEMA_VERSION) {
|
||||||
|
return fail(`version must be "${INSTALLER_SCHEMA_VERSION}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isObject(input.distribution)) return fail('distribution is required');
|
||||||
|
if (!nonEmptyString(input.distribution.packageName)) return fail('distribution.packageName is required');
|
||||||
|
if (!Array.isArray(input.distribution.shimNames) || input.distribution.shimNames.length === 0) {
|
||||||
|
return fail('distribution.shimNames must be a non-empty array');
|
||||||
|
}
|
||||||
|
for (const shimName of input.distribution.shimNames) {
|
||||||
|
if (!nonEmptyString(shimName)) return fail('distribution.shimNames must contain only non-empty strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isObject(input.wrappers)) return fail('wrappers is required');
|
||||||
|
if (!isObject(input.wrappers.windows) || !nonEmptyString(input.wrappers.windows.script)) {
|
||||||
|
return fail('wrappers.windows.script is required');
|
||||||
|
}
|
||||||
|
if (!isObject(input.wrappers.posix) || !nonEmptyString(input.wrappers.posix.script)) {
|
||||||
|
return fail('wrappers.posix.script is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isObject(input.runtime)) return fail('runtime is required');
|
||||||
|
if (!nonEmptyString(input.runtime.start)) return fail('runtime.start is required');
|
||||||
|
if (!nonEmptyString(input.runtime.open)) return fail('runtime.open is required');
|
||||||
|
if (!nonEmptyString(input.runtime.status)) return fail('runtime.status is required');
|
||||||
|
|
||||||
|
if (!isObject(input.driver)) return fail('driver is required');
|
||||||
|
if (input.driver.remediationMode !== DRIVER_REMEDIATION_MODE) {
|
||||||
|
return fail(`driver.remediationMode must be "${DRIVER_REMEDIATION_MODE}"`);
|
||||||
|
}
|
||||||
|
if (input.driver.installSideEffects !== false) {
|
||||||
|
return fail('driver.installSideEffects must be false');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, value: input as unknown as InstallerManifest };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const canonicalInstallerManifest: InstallerManifest = {
|
||||||
|
version: INSTALLER_SCHEMA_VERSION,
|
||||||
|
distribution: {
|
||||||
|
packageName: 'beadboard',
|
||||||
|
shimNames: ['bb', 'beadboard'],
|
||||||
|
},
|
||||||
|
wrappers: {
|
||||||
|
windows: { script: 'install.ps1' },
|
||||||
|
posix: { script: 'install.sh' },
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
start: 'beadboard start',
|
||||||
|
open: 'beadboard open',
|
||||||
|
status: 'beadboard status',
|
||||||
|
},
|
||||||
|
driver: {
|
||||||
|
remediationMode: DRIVER_REMEDIATION_MODE,
|
||||||
|
installSideEffects: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
test('dependency graph uses hide-closed filtered epics for epic chip strip', async () => {
|
||||||
|
const file = await fs.readFile(path.join(process.cwd(), 'src/components/graph/dependency-graph-page.tsx'), 'utf8');
|
||||||
|
|
||||||
|
assert.ok(file.includes('const selectableEpics = useMemo'), 'expected selectableEpics memoized list');
|
||||||
|
assert.ok(file.includes('epics={selectableEpics}'), 'expected EpicChipStrip to receive selectableEpics');
|
||||||
|
assert.ok(file.includes('selectableEpics.some((epic) => epic.id === requestedEpicId)'), 'expected requested epic validation against selectable epics');
|
||||||
|
});
|
||||||
84
tests/components/shared/left-panel-filtering.test.ts
Normal file
84
tests/components/shared/left-panel-filtering.test.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
|
||||||
|
import { shouldHideEpicEntry, type LeftPanelFilters } from '../../../src/components/shared/left-panel';
|
||||||
|
|
||||||
|
const defaultFilters: LeftPanelFilters = {
|
||||||
|
query: '',
|
||||||
|
status: 'all',
|
||||||
|
priority: 'all',
|
||||||
|
preset: 'all',
|
||||||
|
hideClosed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
test('does not hide epics with no children when hideClosed is the only active toggle', () => {
|
||||||
|
const hidden = shouldHideEpicEntry({
|
||||||
|
epicStatus: 'open',
|
||||||
|
matchedChildrenCount: 0,
|
||||||
|
totalChildrenCount: 0,
|
||||||
|
isSelected: false,
|
||||||
|
filters: defaultFilters,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(hidden, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides epics with only closed children when hideClosed is enabled', () => {
|
||||||
|
const hidden = shouldHideEpicEntry({
|
||||||
|
epicStatus: 'open',
|
||||||
|
matchedChildrenCount: 0,
|
||||||
|
totalChildrenCount: 4,
|
||||||
|
isSelected: false,
|
||||||
|
filters: defaultFilters,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(hidden, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides epic with children when query filter excludes all children', () => {
|
||||||
|
const hidden = shouldHideEpicEntry({
|
||||||
|
epicStatus: 'open',
|
||||||
|
matchedChildrenCount: 0,
|
||||||
|
totalChildrenCount: 3,
|
||||||
|
isSelected: false,
|
||||||
|
filters: { ...defaultFilters, query: 'nonexistent' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(hidden, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps selected epic visible even when no children match filters', () => {
|
||||||
|
const hidden = shouldHideEpicEntry({
|
||||||
|
epicStatus: 'open',
|
||||||
|
matchedChildrenCount: 0,
|
||||||
|
totalChildrenCount: 5,
|
||||||
|
isSelected: true,
|
||||||
|
filters: { ...defaultFilters, status: 'blocked' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(hidden, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides closed epic even when it has no children', () => {
|
||||||
|
const hidden = shouldHideEpicEntry({
|
||||||
|
epicStatus: 'closed',
|
||||||
|
matchedChildrenCount: 0,
|
||||||
|
totalChildrenCount: 0,
|
||||||
|
isSelected: false,
|
||||||
|
filters: defaultFilters,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(hidden, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides closed selected epic when hideClosed is enabled', () => {
|
||||||
|
const hidden = shouldHideEpicEntry({
|
||||||
|
epicStatus: 'closed',
|
||||||
|
matchedChildrenCount: 2,
|
||||||
|
totalChildrenCount: 2,
|
||||||
|
isSelected: true,
|
||||||
|
filters: defaultFilters,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(hidden, true);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
test('UnifiedShell clears selected closed epic when hideClosed is enabled', async () => {
|
||||||
|
const file = await fs.readFile(path.join(process.cwd(), 'src/components/shared/unified-shell.tsx'), 'utf8');
|
||||||
|
|
||||||
|
assert.ok(file.includes('if (epic.status === \'closed\' || epic.status === \'tombstone\')'), 'expected closed epic guard');
|
||||||
|
assert.ok(file.includes('setEpicId(null);'), 'expected selected epic reset');
|
||||||
|
});
|
||||||
|
|
@ -49,8 +49,8 @@ test('UnifiedShell - checks bd health and renders setup warning', async () => {
|
||||||
// Test that AssignmentPanel is rendered conditionally based on view and assignMode
|
// Test that AssignmentPanel is rendered conditionally based on view and assignMode
|
||||||
test('UnifiedShell - renders AssignmentPanel conditionally', async () => {
|
test('UnifiedShell - renders AssignmentPanel conditionally', async () => {
|
||||||
const fileContent = await fs.readFile(path.join(process.cwd(), 'src/components/shared/unified-shell.tsx'), 'utf-8');
|
const fileContent = await fs.readFile(path.join(process.cwd(), 'src/components/shared/unified-shell.tsx'), 'utf-8');
|
||||||
// Check for the condition: view === 'graph' && assignMode
|
// Check for the condition: assignMode && !taskId
|
||||||
assert.ok(fileContent.includes("view === 'graph' && assignMode"), 'Should check view === graph && assignMode condition for AssignmentPanel');
|
assert.ok(fileContent.includes("assignMode && !taskId"), 'Should check assignMode && !taskId condition for AssignmentPanel');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test that SwarmWorkspace import is removed (deprecated)
|
// Test that SwarmWorkspace import is removed (deprecated)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue