UI completion pass + accountability & benchmarking
UI (daily-drivable now): - Board: dnd-kit drag-and-drop between columns; click a card → task detail drawer (Sheet) with status, member assignee picker, send-to-AI-seat dispatch, description/artifact, parent/children navigation; seat-triad assignee chips (AI indigo monogram / human slate). - Cartable page (the personal pending slice), Members & invitations page (invite + copy join token; V1 sends no email), Review inbox now shows a word-level diff of your edits vs the proposal (lib/diff.ts, LCS), Org chart page (React Flow: org → teams → seats in the human/open/AI triad). Nav reordered; nothing left "soon". Accountability & benchmarking: - Identity: GET /members (directory + org role) and GET /invitations (with join token, inviter-only) — the directory also resolves names client-side everywhere. - OrgBoard: work_item_transitions recorded on every status change (AddWorkItemTransitions migration); GET /performance — per assignee (human and AI on the same scale): pending by column, done, worked hours (time in InProgress), avg cycle time (start of work → done), plus the unassigned-pending count. Owner-level capability. - Performance page: benchmark table merging board metrics with AI trust metrics (approval rate + edit distance from analytics); flags work with no one accountable. Verified: build green; ArchitectureTests 8/8; IntegrationTests 43/43 (new: directory, invitations list + Member 403s, transition-derived worked-hours/cycle-time, unassigned count); client npm build green (TS strict). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Link, useLocation } from 'react-router'
|
||||
import { Bot, ChartColumn, Inbox, type LucideIcon, LayoutDashboard, LogOut, Network, ShieldCheck } from 'lucide-react'
|
||||
import {
|
||||
Bot,
|
||||
ChartColumn,
|
||||
Gauge,
|
||||
Inbox,
|
||||
type LucideIcon,
|
||||
LayoutDashboard,
|
||||
LogOut,
|
||||
Network,
|
||||
ShieldCheck,
|
||||
Users,
|
||||
} from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -27,11 +38,13 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
|
||||
<nav className="flex flex-1 flex-col gap-1 p-3">
|
||||
<NavItem icon={LayoutDashboard} label="Board" to="/" />
|
||||
<NavItem icon={Bot} label="AI seats" to="/seats" />
|
||||
<NavItem icon={Inbox} label="Cartable" to="/cartable" />
|
||||
<NavItem icon={ShieldCheck} label="Review inbox" to="/reviews" />
|
||||
<NavItem icon={Bot} label="AI seats" to="/seats" />
|
||||
<NavItem icon={Network} label="Org chart" to="/org" />
|
||||
<NavItem icon={Users} label="Members" to="/members" />
|
||||
<NavItem icon={Gauge} label="Performance" to="/performance" />
|
||||
<NavItem icon={ChartColumn} label="Analytics" to="/analytics" />
|
||||
<NavItem icon={Inbox} label="Cartable" muted />
|
||||
<NavItem icon={Network} label="Org chart" muted />
|
||||
</nav>
|
||||
|
||||
<Separator className="bg-sidebar-border" />
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import * as React from "react"
|
||||
import { Dialog as SheetPrimitive } from "radix-ui"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
const SheetClose = SheetPrimitive.Close
|
||||
|
||||
function SheetContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Content>) {
|
||||
return (
|
||||
<SheetPrimitive.Portal>
|
||||
<SheetPrimitive.Overlay
|
||||
data-slot="sheet-overlay"
|
||||
className="fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0"
|
||||
/>
|
||||
<SheetPrimitive.Content
|
||||
data-slot="sheet-content"
|
||||
className={cn(
|
||||
"fixed inset-y-0 right-0 z-50 flex h-full w-full max-w-lg flex-col gap-4 overflow-y-auto border-l bg-background p-6 shadow-lg data-[state=open]:animate-in data-[state=open]:slide-in-from-right data-[state=closed]:animate-out data-[state=closed]:slide-out-to-right",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5", className)} {...props} />
|
||||
}
|
||||
|
||||
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||
return (
|
||||
<SheetPrimitive.Title
|
||||
data-slot="sheet-title"
|
||||
className={cn("text-lg font-semibold tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||
return (
|
||||
<SheetPrimitive.Description
|
||||
data-slot="sheet-description"
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Sheet, SheetClose, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetTrigger }
|
||||
Reference in New Issue
Block a user