feat(IMP-10): D-1 — filtered_section_reasons UI (read-only)

Surface step20_slide_status.json.data.filtered_section_reasons in the
frontend Home header. Verbatim mirror of backend payload — no enum
redefinition, no translation, no auto-classification.

Units:
- u1: FilteredSectionReason interface mirroring src/phase_z2_pipeline.py
  :2217-2278 (10 fields incl. override-uncovered source/position variant).
- u2: RunMeta extension + loadRun() mapping with ?? [] back-compat defaults.
- u3: Header badge + <details> disclosure adjacent to existing status
  badge; hidden when filtered_section_ids.length === 0; renders all 10
  schema fields + filter_reasons[] verbatim.

Scope:
- Frontend-only, read-only. No backend / sync script / Kei·AI panel
  changes. Files: Front/client/src/services/designAgentApi.ts (+20),
  Front/client/src/pages/Home.tsx (+25).

Refs: gitea issue #10 (IMP-10 D-1 filtered_section_reasons UI)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 19:43:13 +09:00
parent 1fb973297f
commit 0fb168befc
2 changed files with 45 additions and 0 deletions

View File

@@ -527,6 +527,31 @@ export default function Home() {
>
{runMeta.status}
</span>
{runMeta.filtered_section_ids.length > 0 && (
<details className="relative">
<summary className="text-[10px] font-bold px-1.5 py-0.5 bg-amber-100 text-amber-700 rounded uppercase tracking-wider cursor-pointer list-none">
Filtered: {runMeta.filtered_section_ids.length}
</summary>
<div className="absolute top-full mt-1 left-0 z-50 bg-white border border-slate-200 rounded shadow-lg p-3 w-96 max-h-96 overflow-y-auto">
{runMeta.filtered_section_reasons.map((r, i) => (
<div key={i} className="mb-2 pb-2 border-b border-slate-100 last:border-0 last:mb-0 last:pb-0 text-[11px]">
<div className="font-mono text-slate-700">{r.section_ids.join(", ")}</div>
<div className="text-slate-500">selection_state: <span className="font-mono">{r.selection_state}</span></div>
{r.merge_type && <div className="text-slate-500">merge_type: <span className="font-mono">{r.merge_type}</span></div>}
{r.template_id && <div className="text-slate-500">template_id: <span className="font-mono">{r.template_id}</span></div>}
{r.v4_label && <div className="text-slate-500">v4_label: <span className="font-mono">{r.v4_label}</span></div>}
{r.phase_z_status && <div className="text-slate-500">phase_z_status: <span className="font-mono">{r.phase_z_status}</span></div>}
{r.score !== null && <div className="text-slate-500">score: <span className="font-mono">{r.score}</span></div>}
{r.source && <div className="text-slate-500">source: <span className="font-mono">{r.source}</span></div>}
{r.position && <div className="text-slate-500">position: <span className="font-mono">{r.position}</span></div>}
<ul className="mt-1 list-disc list-inside text-slate-600">
{r.filter_reasons.map((reason, j) => <li key={j} className="font-mono">{reason}</li>)}
</ul>
</div>
))}
</div>
</details>
)}
</>
)}
</div>

View File

@@ -207,6 +207,22 @@ export async function exportSlidePlan(slidePlan: SlidePlan, userSelection: any):
// step20_slide_status.json → 최종 상태 (PASS / RENDERED_WITH_VISUAL_REGRESSION / ...)
// ─────────────────────────────────────────────────────────────────────────────
// IMP-10 D-1 : verbatim mirror of step20_slide_status.json.data.filtered_section_reasons[]
// schema (src/phase_z2_pipeline.py:2217-2278). `source` / `position` only present on
// the override-uncovered additive variant. Strings rendered verbatim — no enum redefinition.
export interface FilteredSectionReason {
section_ids: string[];
merge_type: string | null;
template_id: string | null;
v4_label: string | null;
phase_z_status: string | null;
score: number | null;
selection_state: string;
filter_reasons: string[];
source?: string;
position?: string | null;
}
export interface RunMeta {
run_id: string;
mdx_path: string;
@@ -214,6 +230,8 @@ export interface RunMeta {
status: "PASS" | "RENDERED_WITH_VISUAL_REGRESSION" | "PARTIAL_COVERAGE" | "ABORTED" | string;
visual_check_passed: boolean;
full_mdx_coverage: boolean;
filtered_section_ids: string[]; // step20 filtered_section_ids
filtered_section_reasons: FilteredSectionReason[]; // step20 filtered_section_reasons
preview_url: string; // /data/runs/{runId}/preview.png
final_html_url: string; // /data/runs/{runId}/final.html
layout_candidates: string[]; // step07 layout_candidates list
@@ -393,6 +411,8 @@ export async function loadRun(runId: string): Promise<LoadRunResult> {
status: slideStatus.data?.overall ?? "UNKNOWN",
visual_check_passed: slideStatus.data?.visual_check_passed ?? false,
full_mdx_coverage: slideStatus.data?.full_mdx_coverage ?? false,
filtered_section_ids: slideStatus.data?.filtered_section_ids ?? [],
filtered_section_reasons: slideStatus.data?.filtered_section_reasons ?? [],
preview_url: `${base}/preview.png`,
final_html_url: `${base}/final.html`,
layout_candidates: layout.data?.layout_candidates ?? [],