Files
KyeongminandClaude Opus 4.8 b836e79ee1 wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷
- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:03:42 +09:00

415 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- Phase Z-2 MVP-1.5b — single slide + Type B layout (top/bottom zones).
원래 Phase Z 설계 복귀: MDX 1 = slide 1, layout preset = zone 분할, frame-derived block ⊂ zone. -->
<!DOCTYPE html>
<html lang="ko"{% if embedded_mode == "embedded" %} class="embedded"{% endif %}>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280">
<title>{{ slide_title }}</title>
{% if embedded_mode == "auto" %}
<script>
(function(){
try {
var params = new URLSearchParams(window.location.search);
if (params.get('embedded') === '1' || window.self !== window.top) {
document.documentElement.classList.add('embedded');
}
} catch (e) {}
})();
</script>
{% endif %}
<style>
/* ── existing tokens (inlined) ── */
{{ token_css | safe }}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
background: #e8ecf0;
display: flex; justify-content: center; align-items: center;
width: 100vw;
height: 100vh;
min-height: 100vh;
overflow: hidden;
word-break: keep-all;
padding: 0;
}
/* ── IMP-14 A-4: embedded mode reset (iframe consumer) ──
standalone-only body centering/min-height/padding undone so the .slide
(1280×720) sits at origin without vertical shift or clipping. */
html.embedded body {
background: transparent;
display: block;
min-height: 0;
padding: 0;
}
html.embedded .slide {
box-shadow: none;
}
/* ── 16:9 슬라이드 (single, 1280×720) ── */
.slide {
width: 1280px; height: 720px;
position: relative;
overflow: hidden;
background: #fff;
box-shadow: 0 4px 20px rgba(0,0,0,.15);
flex-shrink: 0;
}
.slide-bg {
position: absolute; inset: 0;
background: linear-gradient(180deg, #f0f0f0 0%, #ffffff 100%);
z-index: 0;
}
.slide-title {
position: absolute;
left: 52px; top: 22px;
width: calc(100% - 104px);
font-weight: 700;
font-size: var(--font-slide-title);
line-height: var(--lh-slide-title);
background-image: linear-gradient(180deg, #296b55 0%, #123328 100%);
-webkit-background-clip: text; background-clip: text;
color: transparent;
text-shadow: 0 0 2px #322c1e;
z-index: 2;
}
.slide-divider {
position: absolute;
left: 50px; top: 58px;
width: calc(100% - 100px);
height: 2px;
background: #cbd5e1;
z-index: 2;
}
.slide-body {
position: absolute;
left: 50px; top: 76px;
width: calc(100% - 100px); /* 1180 */
height: 585px;
z-index: 1;
overflow: hidden;
}
/* ── Layout preset (composition planner v0 — 8 vocabulary) ──
단일 .layout-{preset} 클래스에 grid CSS 주입.
layout_css = pipeline build_layout_css() 결과 :
- horizontal-2 : rows = dynamic px (content_weight + frame_min_height)
- 그 외 : rows / cols = fr default (LAYOUT_PRESETS)
positions (single / top / bottom / left / right / top-left / ... / grid-2x2 9개)
은 .zone 의 inline grid-area 로 지정 — preset CSS 는 areas 만 정의. */
.layout-{{ layout_preset }} {
display: grid;
grid-template-columns: {{ layout_css.cols }};
grid-template-rows: {{ layout_css.rows }};
grid-template-areas: {{ layout_css.areas | safe }};
gap: {{ gap_px }}px;
height: 100%;
}
/* ── Zone — block 가용 공간 최대화. 프레임 자체 styling 이 boundary 역할 ── */
.zone {
position: relative;
overflow: hidden;
padding: 0;
min-width: 0;
min-height: 0;
}
/* IMP-84: provisional zone visual treatment removed (silent-automation
policy). `data-provisional="1"` attribute is still emitted on the
zone div as silent telemetry for downstream selectors / inspection;
no user-visible outline, wash, or badge. */
/* ── Frame-family text layout contract (shared, reusable) ──
feedback-1 (mvp1.5b_test7): visible improvement 강화.
Stronger hanging indent + breathing line spacing + visible hierarchy. */
.text-line {
font-size: var(--font-body); /* 11px */
font-weight: var(--weight-body);
line-height: 1.6; /* breathing room */
margin: 0;
}
.text-line + .text-line {
margin-top: 4px; /* visible gap between lines (was 2px) */
}
/* bullet line — hanging indent via padding + absolute marker (안정적) */
.text-line--bullet {
padding-left: 14px;
position: relative;
}
.text-line--bullet::before {
content: "•"; /* visible bullet 마커 */
position: absolute;
left: 2px;
top: 0;
font-weight: 700;
line-height: inherit;
color: inherit;
}
/* indent levels — visible nested hierarchy */
.text-line--indent-1 {
padding-left: 30px;
}
.text-line--indent-1::before {
content: "◦"; /* sub-bullet 다른 마커 */
left: 18px;
}
.text-line--indent-2 {
padding-left: 46px;
}
.text-line--indent-2::before {
content: "▪";
left: 34px;
}
/* body line — 마커 없음 */
.text-line--body {
padding-left: 0;
}
.text-line strong { color: #000; font-weight: 700; }
/* ── Transform-block family (frame 29 AS-IS/TO-BE dedicated comparison component) ──
feedback-3: bullet rows 가 아닌 *전용 비교 component*. Header (AS-IS / TO-BE) + paired rows.
color-coded cells, left-border accent → 단번에 *비교 구조* 인지. */
.transform-block {
margin-top: 4px;
padding: 6px 8px 6px;
background: rgba(255, 255, 255, 0.4);
border-radius: 3px;
border: 1px solid rgba(0,0,0,0.05);
}
.transform-block__header {
display: grid;
grid-template-columns: 1fr 22px 1fr;
gap: 6px;
margin-bottom: 4px;
padding-bottom: 3px;
border-bottom: 1px dashed rgba(0,0,0,0.12);
font-size: 9px;
font-weight: 900;
letter-spacing: 0.5px;
}
.transform-block__label--from { color: #6b5444; }
.transform-block__label--to { color: #2563eb; text-align: left; }
.transform-rows {
display: flex; flex-direction: column;
gap: 4px;
}
.transform-row {
display: grid;
grid-template-columns: 1fr 22px 1fr;
gap: 6px;
align-items: stretch;
font-size: var(--font-body);
line-height: 1.45;
}
.transform-row__from {
padding: 3px 8px;
background: linear-gradient(90deg, rgba(140,120,90,0.16), rgba(140,120,90,0.08));
border-left: 2px solid #b8a98a;
border-radius: 2px;
color: #4b3f30;
font-weight: 500;
}
.transform-row__arrow {
text-align: center;
color: #2563eb;
font-weight: 900;
font-size: 14px;
align-self: center;
}
.transform-row__to {
padding: 3px 8px;
background: linear-gradient(90deg, rgba(37,99,235,0.10), rgba(37,99,235,0.18));
border-left: 2px solid #2563eb;
border-radius: 2px;
color: #1e40af;
font-weight: 700;
}
/* ── Footer pill — feedback-3: body 와 weight balance, 살짝 가벼워짐 ── */
.slide-footer {
position: absolute;
left: 50px; bottom: 8px;
width: calc(100% - 100px); /* front 기준 (body 와 정렬) */
height: 41px; /* front 기준 */
border-radius: 999px;
overflow: hidden;
z-index: 2;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(90deg, #3b3523 5%, #263a2a 50%, #113f31 95%);
opacity: 0.92; /* slightly lighter */
}
.slide-footer-text {
position: relative; z-index: 1;
font-size: 15px; /* was 20px (--font-footer) */
font-weight: 700;
line-height: 1.2;
color: #fff;
text-align: center;
text-shadow: 0 0 3px rgba(0,0,0,.4);
letter-spacing: -0.01em;
}
.slide-footer-text em { color: #fe3; font-style: normal; }
.phase-z2-marker {
position: absolute;
top: 4px; right: 8px;
font-size: 9px; color: #94a3b8;
font-family: monospace;
z-index: 3;
}
/* ── IMP-35 u8 : popup details/summary (Step 17 POPUP gate escalation) ──
When the Step 17 POPUP gate escalates a unit (zone.has_popup=True),
slide_base renders a JS-free <details>/<summary> wrapper in the zone.
The body of the frame stays as zone.partial_html (the FIT-version of
content); the popup body holds the FULL original raw_content (MDX 원문
무손실 보존 — 오답노트 #5 / IMPROVEMENT-REDESIGN.md §3.6 line 110).
Placement (default top-right) is read from
zone.popup_binding.detail_trigger.placement
(templates/phase_z2/regions/display_strategies.yaml). HTML-native
<details> per CLAUDE.md 자세히보기 contract — no JavaScript. */
.zone__popup-details {
position: absolute;
z-index: 5;
font-family: 'Pretendard', sans-serif;
}
.zone__popup-details--top-right {
top: 4px;
right: 4px;
}
.zone__popup-details--top-left {
top: 4px;
left: 4px;
}
.zone__popup-details--bottom-right {
bottom: 4px;
right: 4px;
}
.zone__popup-details--bottom-left {
bottom: 4px;
left: 4px;
}
.zone__popup-summary {
list-style: none;
cursor: pointer;
padding: 2px 6px;
background: rgba(30, 41, 59, 0.85);
color: #fff;
border-radius: 2px;
font-size: 9px;
font-weight: 700;
letter-spacing: 0.04em;
line-height: 1.2;
user-select: none;
}
.zone__popup-summary::-webkit-details-marker { display: none; }
.zone__popup-summary::marker { content: ""; }
.zone__popup-body {
position: absolute;
top: 22px;
right: 0;
width: 360px;
max-height: 280px;
overflow: auto;
padding: 8px 10px;
background: #fff;
border: 1px solid var(--color-border, #e2e8f0);
border-radius: 3px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
white-space: pre-wrap;
word-break: keep-all;
font-size: 10px;
line-height: 1.5;
color: #1e293b;
}
/* ── IMP-90 u17 : print mode (Step 22 user-edit + Export).
Companion JS at body end opens popups so FULL raw_content prints. */
@media print {
@page { size: 1280px 720px; margin: 0; }
html, body {
background: #fff !important;
padding: 0 !important;
margin: 0 !important;
min-height: 0 !important;
display: block !important;
}
.slide {
box-shadow: none !important;
page-break-inside: avoid;
break-inside: avoid;
}
.zone__popup-summary { display: none !important; }
.zone__popup-details,
.zone__popup-details[open] { position: static !important; }
.zone__popup-body {
position: static !important;
top: auto !important;
right: auto !important;
max-height: none !important;
overflow: visible !important;
box-shadow: none !important;
border: none !important;
width: auto !important;
padding: 6px 0 0 0 !important;
}
}
</style>
</head>
<body>
<div class="slide" data-page="1">
<div class="slide-bg"></div>
{% if show_debug_marker %}
<div class="phase-z2-marker">phase_z2 / mvp-1.5b / {{ layout_preset }} / single slide</div>
{% endif %}
<div class="slide-title">{{ slide_title }}</div>
<div class="slide-divider"></div>
<div class="slide-body">
<div class="layout-{{ layout_preset }}">
{% for zone in zones %}
<div class="zone" data-zone-position="{{ zone.position }}" data-template-id="{{ zone.template_id }}"{% if zone.provisional %} data-provisional="1"{% endif %}{% if zone.has_popup %} data-has-popup="1"{% endif %} style="grid-area: {{ zone.position }};">
{{ zone.partial_html | safe }}
{% if zone.has_popup %}
{% set _popup_trigger = (zone.popup_binding.detail_trigger if zone.popup_binding else None) or {} %}
{% set _popup_placement = _popup_trigger.placement or 'top-right' %}
{% set _popup_label = _popup_trigger.label or 'details' %}
{% set _popup_strategy = (zone.popup_binding.display_strategy if zone.popup_binding else 'inline_preview_with_details') %}
<details class="zone__popup-details zone__popup-details--{{ _popup_placement }}" data-display-strategy="{{ _popup_strategy }}" data-popup-placement="{{ _popup_placement }}">
<summary class="zone__popup-summary">{{ _popup_label }}</summary>
<div class="zone__popup-body">{{ zone.popup_html }}</div>
</details>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% if slide_footer %}
<div class="slide-footer">
<div class="slide-footer-text">{{ slide_footer | safe }}</div>
</div>
{% endif %}
</div>
<script>
// IMP-90 u17 — beforeprint popup auto-expand (CLAUDE.md 자세히보기 contract).
// Body-level handler (outside any per-zone popup block) so the popup-render
// JS-free invariant (IMP-35 u8) is preserved on the per-zone path.
window.addEventListener('beforeprint', function () {
document.querySelectorAll('details').forEach(function (d) {
d.dataset.imp90PrintRestore = d.open ? '1' : '0';
d.open = true;
});
});
window.addEventListener('afterprint', function () {
document.querySelectorAll('details').forEach(function (d) {
if (d.dataset.imp90PrintRestore === '0') d.open = false;
delete d.dataset.imp90PrintRestore;
});
});
</script>
</body>
</html>