Merge origin/main into feature/assign-archetypes-to-tasks-ui
Resolved conflicts: - .gitignore: kept both bd.sock.startlock and .beadboard/ entries - package.json: kept feature branch test script (explicit enumeration) - API routes: kept dynamic export + isValidProjectRoot from main - globals.css: kept HEAD slideInFromRight animation - use-beads-subscription.ts: kept HEAD onopen handler - realtime.ts: kept main console.log in emit() - snapshot-differ.ts: kept main type-aware dependency diff Blue colors preserved from feature branch.
This commit is contained in:
commit
a8079813b8
28 changed files with 931 additions and 70 deletions
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import type { KanbanFilterOptions, KanbanStats } from '../../lib/kanban';
|
||||
|
|
@ -12,6 +13,7 @@ interface KanbanControlsProps {
|
|||
filters: KanbanFilterOptions;
|
||||
stats: KanbanStats;
|
||||
epics: BeadIssue[];
|
||||
issues: BeadIssue[];
|
||||
onFiltersChange: (filters: KanbanFilterOptions) => void;
|
||||
onNextActionable: () => void;
|
||||
nextActionableFeedback?: string | null;
|
||||
|
|
@ -21,6 +23,7 @@ export function KanbanControls({
|
|||
filters,
|
||||
stats,
|
||||
epics,
|
||||
issues,
|
||||
onFiltersChange,
|
||||
onNextActionable,
|
||||
nextActionableFeedback = null,
|
||||
|
|
@ -29,12 +32,24 @@ export function KanbanControls({
|
|||
'ui-field rounded-xl px-3 py-2.5 text-sm outline-none transition';
|
||||
|
||||
// Build bead counts map for EpicChipStrip
|
||||
const beadCounts = new Map<string, number>();
|
||||
for (const epic of epics) {
|
||||
// Count non-epic issues that belong to this epic
|
||||
const count = epic.dependencies?.filter(d => d.type === 'parent' && d.target === epic.id).length ?? 0;
|
||||
beadCounts.set(epic.id, count);
|
||||
}
|
||||
// Count non-epic issues that have this epic as their parent
|
||||
const beadCounts = useMemo(() => {
|
||||
const counts = new Map<string, number>();
|
||||
for (const epic of epics) {
|
||||
let count = 0;
|
||||
for (const issue of issues) {
|
||||
if (issue.issue_type === 'epic') continue;
|
||||
const parentDep = issue.dependencies.find(d => d.type === 'parent');
|
||||
const inferredParent = issue.id.includes('.') ? issue.id.split('.')[0] : null;
|
||||
const parentEpicId = parentDep?.target ?? inferredParent;
|
||||
if (parentEpicId === epic.id) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
counts.set(epic.id, count);
|
||||
}
|
||||
return counts;
|
||||
}, [epics, issues]);
|
||||
|
||||
return (
|
||||
<section className="grid gap-3">
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ export function KanbanPage({
|
|||
filters={filters}
|
||||
stats={stats}
|
||||
epics={localIssues.filter((issue) => issue.issue_type === 'epic')}
|
||||
issues={localIssues}
|
||||
onFiltersChange={setFilters}
|
||||
onNextActionable={handleNextActionable}
|
||||
nextActionableFeedback={nextActionableFeedback}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Badge } from '@/components/ui/badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { BeadStatus } from '@/lib/types';
|
||||
|
||||
type BeadStatus = 'ready' | 'in_progress' | 'blocked' | 'closed';
|
||||
type BadgeSize = 'sm' | 'md';
|
||||
|
||||
interface StatusBadgeProps {
|
||||
|
|
@ -9,11 +9,14 @@ interface StatusBadgeProps {
|
|||
size?: BadgeSize;
|
||||
}
|
||||
|
||||
const STATUS_CLASSES: Record<BeadStatus, string> = {
|
||||
ready: 'border-teal-500/30 bg-teal-500/15 text-teal-200',
|
||||
const STATUS_CLASSES: Partial<Record<BeadStatus, string>> = {
|
||||
open: 'border-teal-500/30 bg-teal-500/15 text-teal-200',
|
||||
in_progress: 'border-green-500/30 bg-green-500/15 text-green-200',
|
||||
blocked: 'border-amber-500/30 bg-amber-500/15 text-amber-200',
|
||||
deferred: 'border-slate-500/30 bg-slate-500/15 text-slate-300',
|
||||
closed: 'border-slate-500/30 bg-slate-500/15 text-slate-300',
|
||||
pinned: 'border-purple-500/30 bg-purple-500/15 text-purple-200',
|
||||
hooked: 'border-cyan-500/30 bg-cyan-500/15 text-cyan-200',
|
||||
};
|
||||
|
||||
const SIZE_CLASSES: Record<BadgeSize, string> = {
|
||||
|
|
@ -21,24 +24,30 @@ const SIZE_CLASSES: Record<BadgeSize, string> = {
|
|||
md: 'text-xs px-2.5 py-0.5',
|
||||
};
|
||||
|
||||
const STATUS_LABELS: Record<BeadStatus, string> = {
|
||||
ready: 'Ready',
|
||||
const STATUS_LABELS: Partial<Record<BeadStatus, string>> = {
|
||||
open: 'Open',
|
||||
in_progress: 'In Progress',
|
||||
blocked: 'Blocked',
|
||||
deferred: 'Deferred',
|
||||
closed: 'Closed',
|
||||
pinned: 'Pinned',
|
||||
hooked: 'Hooked',
|
||||
};
|
||||
|
||||
export function StatusBadge({ status, size = 'md' }: StatusBadgeProps) {
|
||||
const statusClass = STATUS_CLASSES[status] || 'border-slate-500/30 bg-slate-500/15 text-slate-300';
|
||||
const statusLabel = STATUS_LABELS[status] || status;
|
||||
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'rounded-md border font-semibold',
|
||||
STATUS_CLASSES[status],
|
||||
statusClass,
|
||||
SIZE_CLASSES[size]
|
||||
)}
|
||||
>
|
||||
{STATUS_LABELS[status]}
|
||||
{statusLabel}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue