117 lines
5.3 KiB
TypeScript
117 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Link, useParams } from "react-router-dom";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Search,
|
|
ShieldAlert,
|
|
Code2,
|
|
Palette,
|
|
Workflow,
|
|
CheckCircle2,
|
|
AlertTriangle,
|
|
} from "lucide-react";
|
|
import { Input } from "@/components/ui/input";
|
|
import { cn } from "@/shared/lib/utils";
|
|
import { useProject } from "@/shared/contexts/ProjectContext";
|
|
import { PageHeader } from "@/components/ui/PageHeader";
|
|
|
|
const MemoryRules = () => {
|
|
const { projectId } = useParams();
|
|
const { projects, contextPacks } = useProject();
|
|
const project = projects.find(p => p.id === projectId);
|
|
const rules = project?.memoryRules ?? [];
|
|
const [search, setSearch] = useState("");
|
|
const visibleRules = rules.filter((rule) => `${rule.title} ${rule.description} ${rule.category}`.toLowerCase().includes(search.trim().toLowerCase()));
|
|
const activePack = project?.activeContextPackId
|
|
? contextPacks.find((pack) => pack.id === project.activeContextPackId)
|
|
: undefined;
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<PageHeader
|
|
title="Memory Rules"
|
|
subtitle="Define the constraints and rules that guide your project's development."
|
|
/>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
|
|
<Input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="Search rules..." className="pl-10" aria-label="Search memory rules" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4">
|
|
{visibleRules.map((rule) => (
|
|
<Card key={rule.id} className="hover:shadow-sm transition-shadow animate-fade-in">
|
|
<CardContent className="p-6 flex items-start gap-4">
|
|
<div className="p-2 rounded-lg bg-primary/10 text-primary">
|
|
{rule.category === "UX" && <Palette size={24} />}
|
|
{rule.category === "Technical" && <Code2 size={24} />}
|
|
{rule.category === "Architecture" && <Workflow size={24} />}
|
|
{rule.category === "Do Not Break" && <ShieldAlert size={24} />}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
{rule.category}
|
|
</span>
|
|
<Badge variant={rule.scope === "global" ? "default" : rule.scope === "project" ? "secondary" : "outline"}>
|
|
{rule.scope ?? "project"}
|
|
</Badge>
|
|
<Badge variant="outline" className={cn(
|
|
"text-[10px]",
|
|
rule.priority === "Critical" ? "text-destructive border-destructive/20 bg-destructive/5" : ""
|
|
)}>
|
|
{rule.priority}
|
|
</Badge>
|
|
<Badge variant="secondary" className="text-[10px]">{rule.impact}</Badge>
|
|
{rule.scope === "feature" && <Badge variant="outline">feature-scoped</Badge>}
|
|
{rule.category === "Do Not Break" && (
|
|
<Badge variant="destructive" className="text-[10px]">Do Not Break</Badge>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="text-lg font-semibold">{rule.title}</h3>
|
|
{rule.status === "Verified" ? (
|
|
<CheckCircle2 size={14} className="text-green-500" />
|
|
) : (
|
|
<AlertTriangle size={14} className="text-yellow-500" />
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground mt-1">{rule.description}</p>
|
|
<p className="text-xs text-muted-foreground mt-2">
|
|
Severity: {rule.severity ?? rule.priority} • {rule.linkedFeatureIds?.length ? "linked features" : "no linked features"}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
Include in active context pack:{" "}
|
|
{(rule.includeInContextPacks ?? []).includes(activePack?.id ?? "") ? "yes" : "no"}
|
|
{activePack ? (
|
|
<Link
|
|
to={`/projects/${projectId}/context-packs`}
|
|
className="ml-2 underline underline-offset-4"
|
|
>
|
|
open context packs
|
|
</Link>
|
|
) : null}
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{visibleRules.length === 0 && (
|
|
<div className="text-center py-20 border-2 border-dashed rounded-xl bg-muted/10">
|
|
<ShieldAlert size={48} className="mx-auto text-muted-foreground mb-4" />
|
|
<h3 className="text-lg font-semibold mb-2">{rules.length ? "No matching memory rules" : "No memory rules defined"}</h3>
|
|
<p className="text-muted-foreground">{rules.length ? "Try a broader search." : "Create rules through the database-backed Memory API or Review Queue conversion flow."}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MemoryRules;
|