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,7 +1,6 @@
"use client";
import { AlertTriangle, CheckCircle2, HelpCircle } from "lucide-react";
import { Badge } from "@/components/ui/badge";
type Props = {
drift: boolean;
@@ -9,31 +8,31 @@ type Props = {
coolifyAvailable?: boolean;
};
// Filled pill chips matching IA-redesign mockup 15 (.c-synced / .c-drift).
export function DriftBadge({ drift, coolifyAvailable = true }: Props) {
if (!coolifyAvailable) {
return (
<Badge
variant="outline"
className="text-ink-muted border-rule gap-1"
<span
className="inline-flex items-center gap-1 rounded-full bg-rule-soft text-ink-muted text-[0.72rem] font-semibold px-2.5 py-0.5"
title="Coolify לא זמין — מצב ה-drift לא ידוע"
>
<HelpCircle className="w-3 h-3" />
Unknown
</Badge>
</span>
);
}
if (drift) {
return (
<Badge variant="outline" className="text-warn border-warn/40 gap-1">
<span className="inline-flex items-center gap-1 rounded-full bg-warn-bg text-warn text-[0.72rem] font-semibold px-2.5 py-0.5">
<AlertTriangle className="w-3 h-3" />
Drift
</Badge>
</span>
);
}
return (
<Badge variant="outline" className="text-success border-success/40 gap-1">
<span className="inline-flex items-center gap-1 rounded-full bg-success-bg text-success text-[0.72rem] font-semibold px-2.5 py-0.5">
<CheckCircle2 className="w-3 h-3" />
Synced
</Badge>
</span>
);
}

View File

@@ -3,7 +3,6 @@
import { useState } from "react";
import { ExternalLink, Save, Lock } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import type { McpEnvVar } from "@/lib/api/settings";
import { useUpdateMcpEnv } from "@/lib/api/settings";
import { toast } from "sonner";
@@ -44,36 +43,38 @@ export function EnvVarRow({
`https://coolify.nautilus.marcusgroup.org/project/applications/${coolifyAppUuid}/environment-variables`;
return (
<div className="rounded-md border border-rule p-4 bg-rule-soft/20 hover:bg-rule-soft/40 transition-colors">
<div className="flex items-start justify-between gap-3 mb-3">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<code className="font-mono text-sm font-medium text-navy" dir="ltr">
{spec.key}
</code>
<Badge variant="outline" className="text-[0.7rem]">
{spec.type}
</Badge>
{spec.is_secret && (
<Badge variant="outline" className="text-[0.7rem] text-warn border-warn/40 gap-1">
<Lock className="w-3 h-3" />
secret
</Badge>
)}
<DriftBadge drift={spec.drift} coolifyAvailable={coolifyAvailable} />
{spec.has_duplicates && (
<Badge variant="outline" className="text-[0.7rem] text-warn border-warn/40">
duplicates
</Badge>
)}
</div>
<p className="text-sm text-ink-muted mt-1">{spec.description}</p>
</div>
<div className="px-5 py-4 border-b border-rule-soft last:border-b-0">
{/* envtop — key + type chip + secret chip + drift/synced chip */}
<div className="flex items-center gap-2 flex-wrap">
<code className="font-mono text-[0.84rem] font-semibold text-navy" dir="ltr">
{spec.key}
</code>
<span className="inline-flex items-center rounded-full bg-info-bg text-info text-[0.72rem] font-semibold px-2.5 py-0.5">
{spec.type}
</span>
{spec.is_secret && (
<span className="inline-flex items-center gap-1 rounded-full bg-navy text-white text-[0.72rem] font-semibold px-2.5 py-0.5">
<Lock className="w-3 h-3" />
secret
</span>
)}
<DriftBadge drift={spec.drift} coolifyAvailable={coolifyAvailable} />
{spec.has_duplicates && (
<span className="inline-flex items-center rounded-full bg-warn-bg text-warn text-[0.72rem] font-semibold px-2.5 py-0.5">
duplicates
</span>
)}
</div>
{spec.description && (
<p className="text-[0.82rem] text-ink-muted mt-1">{spec.description}</p>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
<div className="flex items-center gap-2">
<span className="text-[0.72rem] text-ink-muted w-20">Coolify:</span>
{/* vals — Coolify value | Container value (+pending) | save (mockup .vals 3-col) */}
<div className="grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3.5 items-end mt-3.5">
<div>
<span className="block text-[0.72rem] text-ink-muted font-semibold mb-1">
Coolify:
</span>
{spec.is_editable ? (
<EnvVarEditor
spec={spec}
@@ -82,53 +83,61 @@ export function EnvVarRow({
disabled={update.isPending}
/>
) : (
<span className="font-mono text-ink" dir="ltr">
{spec.coolify_value ?? <em className="text-ink-muted"> לא מוגדר </em>}
</span>
)}
</div>
<div className="flex items-center gap-2 flex-wrap">
<span className="text-[0.72rem] text-ink-muted w-20">Container:</span>
<span className="font-mono text-ink" dir="ltr">
{spec.container_value ?? <em className="text-ink-muted"> לא מוגדר </em>}
</span>
{/* ADM-5 (INV-IA5/INV-IA6): when Coolify ≠ Container the container is
running a stale value until a redeploy — say so in plain Hebrew
right here, not only via the top "Drift" badge. */}
{coolifyAvailable && spec.drift && (
<Badge
variant="outline"
className="text-[0.7rem] text-warn border-warn/40"
title="הערך נשמר ב-Coolify אך הקונטיינר עדיין מריץ את הקודם — נדרש redeploy"
<a
href={coolifyEnvUrl}
target="_blank"
rel="noopener noreferrer"
className="font-mono text-[0.81rem] text-gold-deep hover:underline flex items-center gap-1"
dir="ltr"
>
ממתין ל-redeploy
</Badge>
{spec.coolify_value ?? "— לא מוגדר —"}
<ExternalLink className="w-3 h-3 shrink-0" />
</a>
)}
</div>
<div>
<span className="block text-[0.72rem] text-ink-muted font-semibold mb-1">
Container:
</span>
<div className="font-mono text-[0.81rem] text-ink-soft bg-rule-soft rounded-md px-3 py-2 flex items-center gap-2 flex-wrap" dir="ltr">
<span>{spec.container_value ?? "— לא מוגדר —"}</span>
{/* ADM-5 (INV-IA5/INV-IA6): Coolify ≠ Container ⇒ container is stale
until a redeploy — say so in plain Hebrew right here. */}
{coolifyAvailable && spec.drift && (
<span
className="inline-flex items-center rounded-full bg-warn-bg text-warn border border-warn text-[0.7rem] font-semibold px-2 py-0.5"
dir="rtl"
title="הערך נשמר ב-Coolify אך הקונטיינר עדיין מריץ את הקודם — נדרש redeploy"
>
ממתין ל-redeploy
</span>
)}
</div>
</div>
<div className="flex md:block items-end">
{spec.is_editable ? (
<Button
size="sm"
onClick={handleSave}
disabled={!dirty || update.isPending}
variant={dirty ? "default" : "outline"}
className={dirty ? "bg-gold text-white hover:bg-gold-deep border-transparent" : ""}
>
<Save className="w-3.5 h-3.5" data-icon="inline-start" />
{update.isPending ? "שומר..." : "שמור"}
</Button>
) : (
<a
href={coolifyEnvUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 rounded-md border border-rule text-navy px-4 py-1.5 text-[0.81rem] font-semibold hover:bg-gold-wash"
>
ערוך ב-Coolify
<ExternalLink className="w-3 h-3" />
</a>
)}
</div>
</div>
<div className="flex items-center justify-end gap-2 mt-3">
{!spec.is_editable && (
<a
href={coolifyEnvUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[0.78rem] text-gold-deep hover:underline flex items-center gap-1"
>
ערוך ב-Coolify
<ExternalLink className="w-3 h-3" />
</a>
)}
{spec.is_editable && (
<Button
size="sm"
onClick={handleSave}
disabled={!dirty || update.isPending}
>
<Save className="w-3.5 h-3.5" data-icon="inline-start" />
{update.isPending ? "שומר..." : "שמור"}
</Button>
)}
</div>
</div>
);

View File

@@ -72,37 +72,39 @@ export function EnvironmentTab() {
const duplicatesCount = data.vars.filter((v) => v.has_duplicates).length;
return (
<div className="space-y-4">
<Card className="bg-surface border-rule">
<div className="space-y-5">
{/* summary band — Coolify app id + drift/dup counts + redeploy CTA */}
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-4 flex items-center justify-between gap-4 flex-wrap">
<div className="flex items-center gap-3 flex-wrap text-sm">
<Badge variant="outline">
Coolify app: <code dir="ltr" className="ms-1">{data.coolify_app_uuid.slice(0, 8)}</code>
</Badge>
{driftCount > 0 && (
<Badge variant="outline" className="text-warn border-warn/40">
{driftCount} drift
</Badge>
<span className="inline-flex items-center rounded-full bg-warn-bg text-warn text-[0.72rem] font-semibold px-2.5 py-0.5">
{driftCount} ב-Drift
</span>
)}
{duplicatesCount > 0 && (
<Badge variant="outline" className="text-warn border-warn/40">
<span className="inline-flex items-center rounded-full bg-warn-bg text-warn text-[0.72rem] font-semibold px-2.5 py-0.5">
{duplicatesCount} duplicates
</Badge>
</span>
)}
{data.errors.length > 0 && (
<Badge variant="outline" className="text-danger border-danger/40">
<span className="inline-flex items-center rounded-full bg-danger-bg text-danger text-[0.72rem] font-semibold px-2.5 py-0.5">
{data.errors.join(", ")}
</Badge>
</span>
)}
</div>
<Button
onClick={handleRedeploy}
disabled={redeploy.isPending}
variant={pendingRedeploy ? "default" : "outline"}
size="sm"
className={pendingRedeploy ? "bg-gold text-white hover:bg-gold-deep border-transparent" : ""}
variant={pendingRedeploy ? "default" : "outline"}
>
<RefreshCw className={redeploy.isPending ? "w-3.5 h-3.5 animate-spin" : "w-3.5 h-3.5"} data-icon="inline-start" />
{redeploy.isPending ? "Redeploying..." : "Redeploy now"}
{redeploy.isPending ? "פורס מחדש..." : "פרוס מחדש"}
</Button>
</CardContent>
</Card>
@@ -110,28 +112,41 @@ export function EnvironmentTab() {
{CATEGORY_ORDER.map((cat) => {
const vars = grouped.get(cat);
if (!vars || vars.length === 0) return null;
const catDrift = vars.filter((v) => v.drift).length;
return (
<Card key={cat} className="bg-surface border-rule">
<CardContent className="px-6 py-5">
<h2 className="text-navy text-lg mb-4 flex items-center gap-2">
{CATEGORY_LABELS[cat]}
<Badge variant="outline" className="text-[0.7rem] tabular-nums">
{vars.length}
</Badge>
</h2>
<div className="space-y-3">
{vars.map((v) => (
<EnvVarRow
key={v.key}
spec={v}
coolifyAppUuid={data.coolify_app_uuid}
coolifyAvailable={coolifyAvailable}
onPendingRedeploy={() => setPendingRedeploy(true)}
/>
))}
<div
key={cat}
className="rounded-lg border border-rule bg-surface shadow-sm overflow-hidden"
>
{/* panel header (.ph) — parchment band: title + count + drift chip */}
<div className="flex items-center justify-between gap-3 px-5 py-4 border-b border-rule bg-parchment">
<div>
<h2 className="text-navy text-base font-semibold mb-0 flex items-center gap-2">
{CATEGORY_LABELS[cat]}
<span className="text-[0.72rem] text-ink-muted font-medium tabular-nums">
({vars.length})
</span>
</h2>
<p className="text-[0.78rem] text-ink-muted mt-0.5">
מקור-האמת הוא Coolify. שינוי נכנס לתוקף רק לאחר redeploy של הקונטיינר.
</p>
</div>
</CardContent>
</Card>
{coolifyAvailable && catDrift > 0 && (
<span className="inline-flex items-center rounded-full bg-warn-bg text-warn text-[0.72rem] font-semibold px-2.5 py-0.5 shrink-0">
{catDrift} ב-Drift
</span>
)}
</div>
{vars.map((v) => (
<EnvVarRow
key={v.key}
spec={v}
coolifyAppUuid={data.coolify_app_uuid}
coolifyAvailable={coolifyAvailable}
onPendingRedeploy={() => setPendingRedeploy(true)}
/>
))}
</div>
);
})}
</div>

View File

@@ -11,60 +11,92 @@ import { RegistrationsTab } from "./_components/registrations-tab";
import { BlocksTab } from "./_components/blocks-tab";
import { AgentsTab } from "./_components/agents-tab";
/*
* Settings — IA-redesign composition (mockup 15): a header band with a
* top-end "redeploy" deep-link, then a two-column layout — a vertical
* sidenav (section list) + the active section panel. Implemented on top of
* shadcn Tabs so all six sections and their logic-heavy contents
* (Paperclip, agents, env vars w/ drift+redeploy, tools, blocks,
* registrations) are preserved verbatim; only the chrome is restyled from
* a horizontal tab strip to the approved sidenav layout.
*/
const COOLIFY_APP_UUID = "gyjo0mtw2c42ej3xxvbz8zio";
const COOLIFY_REDEPLOY_URL = `https://coolify.nautilus.marcusgroup.org/project/applications/${COOLIFY_APP_UUID}`;
const SECTIONS = [
{ value: "paperclip", label: "Paperclip / סוכנים", icon: Building2 },
{ value: "agents", label: "סוכנים", icon: Bot },
{ value: "environment", label: "משתני סביבה", icon: Server },
{ value: "tools", label: "כלי MCP", icon: Wrench },
{ value: "blocks", label: "בלוקים", icon: Layers },
{ value: "registrations", label: "רישומים", icon: Plug },
] as const;
export default function SettingsPage() {
return (
<AppShell>
<section className="space-y-6">
<header>
<nav className="text-[0.78rem] text-ink-muted mb-1">
<Link href="/" className="hover:text-gold-deep">
בית
</Link>
<span aria-hidden> · </span>
<span className="text-navy">הגדרות</span>
</nav>
<h1 className="text-navy mb-0">הגדרות</h1>
<p className="text-ink-muted text-sm mt-1 max-w-2xl">
תצורת המערכת, MCP server, ו-Paperclip integration.
</p>
<header className="flex items-end justify-between gap-4 flex-wrap">
<div>
<nav className="text-[0.78rem] text-ink-muted mb-1">
<Link href="/" className="hover:text-gold-deep">
בית
</Link>
<span aria-hidden> · </span>
<span className="text-navy">הגדרות</span>
</nav>
<h1 className="text-navy mb-0">הגדרות</h1>
<p className="text-ink-muted text-sm mt-1 max-w-2xl">
תצורת הפלטפורמה סוכנים, סביבה, כלים ובלוקים.
</p>
</div>
<a
href={COOLIFY_REDEPLOY_URL}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 rounded-lg bg-gold text-white hover:bg-gold-deep px-5 py-2.5 text-sm font-semibold transition-colors"
>
פרוס מחדש (redeploy)
</a>
</header>
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
<Tabs dir="rtl" defaultValue="paperclip" className="space-y-4">
<TabsList>
<TabsTrigger value="paperclip">
<Building2 className="w-4 h-4" data-icon="inline-start" />
Paperclip
</TabsTrigger>
<TabsTrigger value="agents">
<Bot className="w-4 h-4" data-icon="inline-start" />
סוכנים
</TabsTrigger>
<TabsTrigger value="environment">
<Server className="w-4 h-4" data-icon="inline-start" />
סביבה
</TabsTrigger>
<TabsTrigger value="tools">
<Wrench className="w-4 h-4" data-icon="inline-start" />
כלים
</TabsTrigger>
<TabsTrigger value="blocks">
<Layers className="w-4 h-4" data-icon="inline-start" />
בלוקים
</TabsTrigger>
<TabsTrigger value="registrations">
<Plug className="w-4 h-4" data-icon="inline-start" />
רישומים
</TabsTrigger>
<Tabs
dir="rtl"
defaultValue="environment"
orientation="vertical"
className="flex-row gap-6 items-start"
>
{/* vertical sidenav (mockup .sidenav) */}
<TabsList
variant="line"
className="w-[230px] shrink-0 items-stretch gap-0 rounded-lg border border-rule bg-surface shadow-sm overflow-hidden p-0"
>
{SECTIONS.map((s) => {
const Icon = s.icon;
return (
<TabsTrigger
key={s.value}
value={s.value}
className="relative justify-start gap-2 rounded-none border-b border-rule-soft last:border-b-0 px-4 py-3 text-sm font-medium text-ink-soft data-active:bg-gold-wash data-active:text-gold-deep data-active:font-semibold data-active:after:opacity-100 data-active:after:bg-gold hover:bg-gold-wash/40"
>
<Icon className="w-4 h-4 shrink-0" />
{s.label}
</TabsTrigger>
);
})}
</TabsList>
<TabsContent value="paperclip"><PaperclipTab /></TabsContent>
<TabsContent value="agents"><AgentsTab /></TabsContent>
<TabsContent value="environment"><EnvironmentTab /></TabsContent>
<TabsContent value="tools"><ToolsTab /></TabsContent>
<TabsContent value="blocks"><BlocksTab /></TabsContent>
<TabsContent value="registrations"><RegistrationsTab /></TabsContent>
<div className="flex-1 min-w-0">
<TabsContent value="paperclip" className="mt-0"><PaperclipTab /></TabsContent>
<TabsContent value="agents" className="mt-0"><AgentsTab /></TabsContent>
<TabsContent value="environment" className="mt-0"><EnvironmentTab /></TabsContent>
<TabsContent value="tools" className="mt-0"><ToolsTab /></TabsContent>
<TabsContent value="blocks" className="mt-0"><BlocksTab /></TabsContent>
<TabsContent value="registrations" className="mt-0"><RegistrationsTab /></TabsContent>
</div>
</Tabs>
</section>
</AppShell>