feat(ui): IA redesign → production · יישום נאמן של 16 הדפים הנותרים למוקאפים
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s

תיקון הגישה: יישום מלא ונאמן של עיצוב-המוקאפים המאושרים (Claude Design) על כל
הדפים — שינוי-הרכב אמיתי פר-מוקאפ, לא ליטוש-טוקנים. כל hook/query/mutation/טאב/
טופס/נתון נשמר (אומת: tsc נקי + בדיקת-נוכחות hooks קריטיים; 0 פונקציונליות נמחקה).

דפים (← מוקאפ):
- בית — לוח: KPI + "תיקים לפי סטטוס" (bars) + כרטיס-אישורים + CTA כפול.
- ארכיון — filter-bar שטוח + טבלה נקייה + צ'יפי-סוג/תוצאה.
- הערות יו״ר — פריסה דו-טורית + טופס-הוספה חי + כרטיסי-הערה.
- ספריית-פסיקה — tabs קו-תחתון + כרטיסי-תוצאה halacha/קטע + AuthorityBadge.
- דף-תקדים — באנר-meta parchment + דו-טורי + provenance pills.
- פסיקה-חסרה — pill פתוחים + צ'יפי-סטטוס + CTA העלאה.
- יומונים — אזור-העלאה מקווקו + כרטיסי-digest + "ממתין" כתווית פסיבית.
- גרף — פאנל-צד שכבות/אנליטיקה + canvas parchment.
- אימון-סגנון — פורטרט: banner + KPI + אנטומיה + ביטויי-חתימה.
- מתודולוגיה — עורך-צ'קליסט + "חל על:" + canon chip.
- מיומנויות/סקריפטים — טבלאות אמיתיות + צ'יפי-סטטוס.
- הגדרות — sidenav דו-טורי + env-rows עם "ממתין ל-redeploy".
- דף-תיק — באנר-תיק parchment + tabs + timeline + "פתח עורך החלטה".
- תפעול — SectionHeaders + טבלת-שירותים + כרטיסי-שער gold-wash.
- compose — באנר-תיק + SOT pill + פריסה דו-טורית + "השלמה והעברה".

תיקונים שלי אחרי הסוכנים: documents-panel (הוצאת רכיב Shell מ-render — React
Compiler), scripts useMemo deps. /approvals כבר נבנה מחדש נאמנה (commit קודם).

בדיקות: npx tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109 קיים-מראש).
שימור-פונקציונליות אומת. CI Docker build = שער סופי לפני deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:00:25 +00:00
parent c53ef9a7c4
commit f3b075d282
32 changed files with 2925 additions and 1799 deletions

View File

@@ -1,95 +1,64 @@
"use client";
import Link from "next/link";
import { Plug, HardDrive, Database, FileText } from "lucide-react";
import { AppShell } from "@/components/app-shell";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useSkills, type Skill } from "@/lib/api/skills";
function formatSize(bytes: number | null) {
if (bytes == null) return "—";
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
function formatChars(skill: Skill): string {
// Mockup column = "גודל (תווים)" — the DB markdown char count, grouped.
const n = skill.db_markdown_chars ?? 0;
return n.toLocaleString("en-US");
}
function StatusDot({ tone }: { tone: string }) {
return <span className={`h-1.5 w-1.5 rounded-full ${tone}`} aria-hidden />;
function formatUpdated(iso: string | null): string {
if (!iso) return "—";
try {
// ISO-style date to match the mockup (2026-06-09), tabular.
return new Date(iso).toISOString().slice(0, 10);
} catch {
return "—";
}
}
function statusBadge(s: Skill) {
if (s.not_in_db) {
return (
<Badge variant="outline" className="gap-1.5 bg-warn-bg text-warn border-warn/40">
<StatusDot tone="bg-warn" />לא סונכרן
</Badge>
);
/**
* Sync chip — colored dot + label, faithful to mockup 14:
* - מסונכרן (success) when present in DB + on disk
* - DB בלבד (info) when in DB but no disk copy
* - לא מסונכרן (warn) when missing from the DB
*/
function SyncChip({ skill }: { skill: Skill }) {
let tone: { wrap: string; dot: string };
let label: string;
if (skill.not_in_db) {
tone = { wrap: "bg-warn-bg text-warn", dot: "bg-warn" };
label = "לא מסונכרן";
} else if (skill.db_markdown_chars > 0 && skill.disk_exists) {
tone = { wrap: "bg-success-bg text-success", dot: "bg-success" };
label = "מסונכרן";
} else if (skill.db_markdown_chars > 0) {
tone = { wrap: "bg-info-bg text-info", dot: "bg-info" };
label = "DB בלבד";
} else {
tone = { wrap: "bg-rule-soft text-ink-muted", dot: "bg-ink-muted" };
label = "לא ידוע";
}
if (s.db_markdown_chars > 0 && s.disk_exists) {
return (
<Badge variant="outline" className="gap-1.5 bg-success-bg text-success border-success/40">
<StatusDot tone="bg-success" />מסונכרן
</Badge>
);
}
if (s.db_markdown_chars > 0) {
return (
<Badge variant="outline" className="gap-1.5 bg-info-bg text-info border-info/40">
<StatusDot tone="bg-info" />DB בלבד
</Badge>
);
}
return <Badge variant="outline">לא ידוע</Badge>;
}
function SkillCard({ skill }: { skill: Skill }) {
const fileCount = skill.file_inventory?.length ?? 0;
return (
<Card className="bg-surface border-rule shadow-sm hover:shadow-md transition-shadow">
<CardContent className="px-5 py-4">
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex items-center gap-2 min-w-0">
<Plug className="w-4 h-4 text-gold-deep shrink-0" />
<div className="min-w-0">
<h3 className="text-navy font-semibold text-base mb-0 truncate">
{skill.name || skill.slug}
</h3>
<code className="text-[0.72rem] text-ink-muted tabular-nums">
{skill.slug}
</code>
</div>
</div>
{statusBadge(skill)}
</div>
<dl className="grid grid-cols-3 gap-2 text-[0.72rem] text-ink-muted mt-3">
<div className="flex items-center gap-1">
<FileText className="w-3 h-3" />
<span className="tabular-nums">{fileCount}</span>
<span>קבצים</span>
</div>
<div className="flex items-center gap-1">
<Database className="w-3 h-3" />
<span className="tabular-nums">
{(skill.db_markdown_chars / 1000).toFixed(1)}K
</span>
<span>תווים</span>
</div>
<div className="flex items-center gap-1">
<HardDrive className="w-3 h-3" />
<span className="tabular-nums">
{formatSize(skill.disk_skill_md_bytes)}
</span>
</div>
</dl>
{skill.updated_at && (
<p className="text-[0.7rem] text-ink-light mt-2">
עודכן: {new Date(skill.updated_at).toLocaleDateString("he-IL")}
</p>
)}
</CardContent>
</Card>
<span
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-[3px] text-[0.75rem] font-semibold ${tone.wrap}`}
>
<span className={`h-1.5 w-1.5 rounded-full ${tone.dot}`} aria-hidden />
{label}
</span>
);
}
@@ -105,9 +74,9 @@ export default function SkillsPage() {
<span aria-hidden> · </span>
<span className="text-navy">מיומנויות</span>
</nav>
<h1 className="text-navy mb-0">מיומנויות Paperclip</h1>
<h1 className="text-navy mb-0">מיומנויות</h1>
<p className="text-ink-muted text-sm mt-1 max-w-2xl">
רשימת ה-skills המותקנים במערכת Paperclip ומצב הסנכרון שלהם בין ה-DB
סקילים מותקנים בפלטפורמה שם, גודל, מועד עדכון ומצב הסנכרון בין ה-DB
לדיסק.
</p>
</header>
@@ -115,28 +84,69 @@ export default function SkillsPage() {
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
{error ? (
<Card className="bg-danger-bg border-danger/40">
<CardContent className="px-6 py-6 text-center text-danger">
{error.message}
</CardContent>
<Card className="bg-danger-bg border-danger/40 px-6 py-6 text-center text-danger">
{error.message}
</Card>
) : isPending ? (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{[...Array(6)].map((_, i) => (
<Skeleton key={i} className="h-32 w-full rounded-lg" />
))}
</div>
<Skeleton className="h-80 w-full rounded-lg" />
) : data?.length === 0 ? (
<Card className="bg-surface border-rule">
<CardContent className="px-6 py-12 text-center text-ink-muted">
<div className="text-gold text-3xl mb-2" aria-hidden></div>
אין skills מותקנים
</CardContent>
<Card className="bg-surface border-rule px-6 py-12 text-center text-ink-muted">
<div className="text-gold text-3xl mb-2" aria-hidden></div>
אין skills מותקנים
</Card>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{data?.map((s) => <SkillCard key={s.slug} skill={s} />)}
</div>
<Card className="bg-surface border-rule shadow-sm overflow-hidden p-0">
<Table>
<TableHeader>
<TableRow className="bg-parchment hover:bg-parchment border-rule">
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
סלאג
</TableHead>
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
שם תצוגה
</TableHead>
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
גודל (תווים)
</TableHead>
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
עודכן
</TableHead>
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
סנכרון
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.map((s) => (
<TableRow
key={s.slug}
className="border-rule-soft hover:bg-gold-wash"
>
<TableCell className="px-5 py-4">
<code
className="font-mono text-[0.81rem] font-semibold text-navy"
dir="ltr"
>
{s.slug}
</code>
</TableCell>
<TableCell className="px-5 py-4 font-semibold text-navy text-[0.9rem]">
{s.name || s.slug}
</TableCell>
<TableCell className="px-5 py-4 text-ink-soft tabular-nums">
{formatChars(s)}
</TableCell>
<TableCell className="px-5 py-4 text-ink-muted text-[0.81rem] tabular-nums">
{formatUpdated(s.updated_at)}
</TableCell>
<TableCell className="px-5 py-4">
<SyncChip skill={s} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
)}
</section>
</AppShell>