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>