Merge pull request 'feat(ui): באדג'י synthesis/superseded + provenance ב-lessons-tab (#162)' (#352) from worktree-lessons-synthesis-badges into main
This commit was merged in pull request #352.
This commit is contained in:
@@ -2655,7 +2655,7 @@ async def list_decision_lessons(corpus_id: UUID) -> list[dict]:
|
|||||||
# review_status is the single writer gate).
|
# review_status is the single writer gate).
|
||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"SELECT id, style_corpus_id, lesson_text, category, source, "
|
"SELECT id, style_corpus_id, lesson_text, category, source, "
|
||||||
" review_status, created_by, created_at, updated_at "
|
" review_status, created_by, created_at, updated_at, synthesized_from "
|
||||||
"FROM decision_lessons WHERE style_corpus_id = $1 "
|
"FROM decision_lessons WHERE style_corpus_id = $1 "
|
||||||
"ORDER BY created_at DESC",
|
"ORDER BY created_at DESC",
|
||||||
corpus_id,
|
corpus_id,
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ const SOURCE_BADGE: Record<string, { label: string; cls: string }> = {
|
|||||||
curator: { label: "הרמס (סקירה)", cls: "bg-info-bg text-info" },
|
curator: { label: "הרמס (סקירה)", cls: "bg-info-bg text-info" },
|
||||||
style_analyzer: { label: "מנתח-סגנון", cls: "bg-success-bg text-success" },
|
style_analyzer: { label: "מנתח-סגנון", cls: "bg-success-bg text-success" },
|
||||||
"panel:deepseek+gemini": { label: "פאנל: דיפסיק+גמיני", cls: "bg-gold-wash text-gold-deep" },
|
"panel:deepseek+gemini": { label: "פאנל: דיפסיק+גמיני", cls: "bg-gold-wash text-gold-deep" },
|
||||||
|
// #158/INV-LRN8 — a super-lesson merging overlapping lessons; navy-filled so it
|
||||||
|
// reads as the consolidated/elevated one.
|
||||||
|
synthesis: { label: "סינתזה", cls: "bg-navy text-parchment" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const SOURCE_BADGE_FALLBACK = { label: "פאנל", cls: "bg-rule-soft text-ink-soft" };
|
const SOURCE_BADGE_FALLBACK = { label: "פאנל", cls: "bg-rule-soft text-ink-soft" };
|
||||||
@@ -63,6 +66,9 @@ const REVIEW_BADGE: Record<string, { label: string; cls: string }> = {
|
|||||||
proposed: { label: "ממתין לאישור", cls: "bg-gold-wash text-gold-deep border-gold/40" },
|
proposed: { label: "ממתין לאישור", cls: "bg-gold-wash text-gold-deep border-gold/40" },
|
||||||
approved: { label: "מאושר · זורם לכותב", cls: "bg-success-bg text-success border-success/40" },
|
approved: { label: "מאושר · זורם לכותב", cls: "bg-success-bg text-success border-success/40" },
|
||||||
rejected: { label: "נדחה", cls: "bg-rule-soft text-ink-muted line-through" },
|
rejected: { label: "נדחה", cls: "bg-rule-soft text-ink-muted line-through" },
|
||||||
|
// #158/INV-LRN8 — merged into a synthesis super-lesson; kept as provenance, no
|
||||||
|
// longer fed to the writer. Muted (not struck — it wasn't rejected).
|
||||||
|
superseded: { label: "מוזג", cls: "bg-rule-soft text-ink-muted" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LessonsTab({ corpusId }: { corpusId: string }) {
|
export function LessonsTab({ corpusId }: { corpusId: string }) {
|
||||||
@@ -201,8 +207,10 @@ function LessonItem({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mergedCount = lesson.source === "synthesis" ? (lesson.synthesized_from?.length ?? 0) : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-surface border-rule">
|
<Card className={`bg-surface border-rule ${lesson.review_status === "superseded" ? "opacity-70" : ""}`}>
|
||||||
<CardContent className="px-4 py-3 space-y-2">
|
<CardContent className="px-4 py-3 space-y-2">
|
||||||
<div className="flex items-center gap-2 text-[0.72rem]">
|
<div className="flex items-center gap-2 text-[0.72rem]">
|
||||||
<Badge variant="outline"
|
<Badge variant="outline"
|
||||||
@@ -220,6 +228,12 @@ function LessonItem({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{mergedCount > 0 && (
|
||||||
|
<div className="text-[0.72rem] text-ink-muted">
|
||||||
|
⤷ מוזג מ-<b className="text-navy font-semibold">{mergedCount} לקחים</b> · דחייה תשחזר אותם
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<>
|
<>
|
||||||
<Textarea value={text} onChange={(e) => setText(e.target.value)}
|
<Textarea value={text} onChange={(e) => setText(e.target.value)}
|
||||||
|
|||||||
@@ -504,14 +504,20 @@ export type DecisionLesson = {
|
|||||||
category: "style" | "structure" | "lexicon" | "tabular" | "general";
|
category: "style" | "structure" | "lexicon" | "tabular" | "general";
|
||||||
// "panel:deepseek+gemini" — the two-judge style panel; (string) keeps it open
|
// "panel:deepseek+gemini" — the two-judge style panel; (string) keeps it open
|
||||||
// to future panel sources without a type break.
|
// to future panel sources without a type break.
|
||||||
source: "manual" | "curator" | "chair" | "style_analyzer" | (string & {});
|
// "synthesis" — a super-lesson merging overlapping lessons (#158/INV-LRN8);
|
||||||
|
// "panel:deepseek+gemini" — the two-judge style panel; (string) keeps it open.
|
||||||
|
source: "manual" | "curator" | "chair" | "style_analyzer" | "synthesis" | (string & {});
|
||||||
// review gate (INV-LRN1/G10): only "approved" lessons flow to the writer.
|
// review gate (INV-LRN1/G10): only "approved" lessons flow to the writer.
|
||||||
// Panel-written lessons start as "proposed" and wait for the chair here.
|
// Panel-written lessons start as "proposed" and wait for the chair here.
|
||||||
|
// "superseded" — a source lesson merged into a synthesis (#158); kept as
|
||||||
|
// provenance, no longer fed to the writer.
|
||||||
// (applied_to_skill removed — LRN-1/INV-IA3: review_status is the single gate.)
|
// (applied_to_skill removed — LRN-1/INV-IA3: review_status is the single gate.)
|
||||||
review_status: "proposed" | "approved" | "rejected";
|
review_status: "proposed" | "approved" | "rejected" | "superseded";
|
||||||
created_by: string;
|
created_by: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
// #158/INV-LRN8: ids of the source lessons a 'synthesis' row merged (provenance).
|
||||||
|
synthesized_from?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LessonReviewStatus = DecisionLesson["review_status"];
|
export type LessonReviewStatus = DecisionLesson["review_status"];
|
||||||
|
|||||||
@@ -1544,6 +1544,9 @@ def _lesson_to_json(row: dict) -> dict:
|
|||||||
"created_by": row.get("created_by", ""),
|
"created_by": row.get("created_by", ""),
|
||||||
"created_at": row["created_at"].isoformat() if row.get("created_at") else "",
|
"created_at": row["created_at"].isoformat() if row.get("created_at") else "",
|
||||||
"updated_at": row["updated_at"].isoformat() if row.get("updated_at") else "",
|
"updated_at": row["updated_at"].isoformat() if row.get("updated_at") else "",
|
||||||
|
# #158/INV-LRN8: source lessons a 'synthesis' super-lesson merged (provenance);
|
||||||
|
# empty for non-synthesis rows. Lets the UI show "מוזג מ-N לקחים".
|
||||||
|
"synthesized_from": [str(x) for x in (row.get("synthesized_from") or [])],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user