Files
soneta-erp-skills/soneta-form-xml-workspace/iteration-1/review.html
T
2026-03-05 15:31:31 +01:00

1326 lines
85 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Eval Review</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
<script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js" integrity="sha384-EnyY0/GSHQGSxSgMwaIPzSESbqoOLSexfnSMN2AP+39Ckmn92stwABZynq1JyzdT" crossorigin="anonymous"></script>
<style>
:root {
--bg: #faf9f5;
--surface: #ffffff;
--border: #e8e6dc;
--text: #141413;
--text-muted: #b0aea5;
--accent: #d97757;
--accent-hover: #c4613f;
--green: #788c5d;
--green-bg: #eef2e8;
--red: #c44;
--red-bg: #fceaea;
--header-bg: #141413;
--header-text: #faf9f5;
--radius: 6px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Lora', Georgia, serif;
background: var(--bg);
color: var(--text);
height: 100vh;
display: flex;
flex-direction: column;
}
/* ---- Header ---- */
.header {
background: var(--header-bg);
color: var(--header-text);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.header h1 {
font-family: 'Poppins', sans-serif;
font-size: 1.25rem;
font-weight: 600;
}
.header .instructions {
font-size: 0.8rem;
opacity: 0.7;
margin-top: 0.25rem;
}
.header .progress {
font-size: 0.875rem;
opacity: 0.8;
text-align: right;
}
/* ---- Main content ---- */
.main {
flex: 1;
overflow-y: auto;
padding: 1.5rem 2rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
/* ---- Sections ---- */
.section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
flex-shrink: 0;
}
.section-header {
font-family: 'Poppins', sans-serif;
padding: 0.75rem 1rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
background: var(--bg);
}
.section-body {
padding: 1rem;
}
/* ---- Config badge ---- */
.config-badge {
display: inline-block;
padding: 0.2rem 0.625rem;
border-radius: 9999px;
font-family: 'Poppins', sans-serif;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-left: 0.75rem;
vertical-align: middle;
}
.config-badge.config-primary {
background: rgba(33, 150, 243, 0.12);
color: #1976d2;
}
.config-badge.config-baseline {
background: rgba(255, 193, 7, 0.15);
color: #f57f17;
}
/* ---- Prompt ---- */
.prompt-text {
white-space: pre-wrap;
font-size: 0.9375rem;
line-height: 1.6;
}
/* ---- Outputs ---- */
.output-file {
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.output-file + .output-file {
margin-top: 1rem;
}
.output-file-header {
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
font-weight: 600;
color: var(--text-muted);
background: var(--bg);
border-bottom: 1px solid var(--border);
font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
display: flex;
justify-content: space-between;
align-items: center;
}
.output-file-header .dl-btn {
font-size: 0.7rem;
color: var(--accent);
text-decoration: none;
cursor: pointer;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 500;
opacity: 0.8;
}
.output-file-header .dl-btn:hover {
opacity: 1;
text-decoration: underline;
}
.output-file-content {
padding: 0.75rem;
overflow-x: auto;
}
.output-file-content pre {
font-size: 0.8125rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
}
.output-file-content img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
.output-file-content iframe {
width: 100%;
height: 600px;
border: none;
}
.output-file-content table {
border-collapse: collapse;
font-size: 0.8125rem;
width: 100%;
}
.output-file-content table td,
.output-file-content table th {
border: 1px solid var(--border);
padding: 0.375rem 0.5rem;
text-align: left;
}
.output-file-content table th {
background: var(--bg);
font-weight: 600;
}
.output-file-content .download-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--accent);
text-decoration: none;
font-size: 0.875rem;
cursor: pointer;
}
.output-file-content .download-link:hover {
background: var(--border);
}
.empty-state {
color: var(--text-muted);
font-style: italic;
padding: 2rem;
text-align: center;
}
/* ---- Feedback ---- */
.prev-feedback {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.625rem 0.75rem;
margin-top: 0.75rem;
font-size: 0.8125rem;
color: var(--text-muted);
line-height: 1.5;
}
.prev-feedback-label {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.25rem;
color: var(--text-muted);
}
.feedback-textarea {
width: 100%;
min-height: 100px;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: 4px;
font-family: inherit;
font-size: 0.9375rem;
line-height: 1.5;
resize: vertical;
color: var(--text);
}
.feedback-textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.feedback-status {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 0.5rem;
min-height: 1.1em;
}
/* ---- Grades (collapsible) ---- */
.grades-toggle {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.grades-toggle:hover {
color: var(--accent);
}
.grades-toggle .arrow {
margin-right: 0.5rem;
transition: transform 0.15s;
font-size: 0.75rem;
}
.grades-toggle .arrow.open {
transform: rotate(90deg);
}
.grades-content {
display: none;
margin-top: 0.75rem;
}
.grades-content.open {
display: block;
}
.grades-summary {
font-size: 0.875rem;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.grade-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
.grade-pass { background: var(--green-bg); color: var(--green); }
.grade-fail { background: var(--red-bg); color: var(--red); }
.assertion-list {
list-style: none;
}
.assertion-item {
padding: 0.625rem 0;
border-bottom: 1px solid var(--border);
font-size: 0.8125rem;
}
.assertion-item:last-child { border-bottom: none; }
.assertion-status {
font-weight: 600;
margin-right: 0.5rem;
}
.assertion-status.pass { color: var(--green); }
.assertion-status.fail { color: var(--red); }
.assertion-evidence {
color: var(--text-muted);
font-size: 0.75rem;
margin-top: 0.25rem;
padding-left: 1.5rem;
}
/* ---- View tabs ---- */
.view-tabs {
display: flex;
gap: 0;
padding: 0 2rem;
background: var(--bg);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.view-tab {
font-family: 'Poppins', sans-serif;
padding: 0.625rem 1.25rem;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
border: none;
background: none;
color: var(--text-muted);
border-bottom: 2px solid transparent;
transition: all 0.15s;
}
.view-tab:hover { color: var(--text); }
.view-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.view-panel { display: none; }
.view-panel.active { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
/* ---- Benchmark view ---- */
.benchmark-view {
padding: 1.5rem 2rem;
overflow-y: auto;
flex: 1;
}
.benchmark-table {
border-collapse: collapse;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 0.8125rem;
width: 100%;
margin-bottom: 1.5rem;
}
.benchmark-table th, .benchmark-table td {
padding: 0.625rem 0.75rem;
text-align: left;
border: 1px solid var(--border);
}
.benchmark-table th {
font-family: 'Poppins', sans-serif;
background: var(--header-bg);
color: var(--header-text);
font-weight: 500;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.benchmark-table tr:hover { background: var(--bg); }
.benchmark-table tr.benchmark-row-with { background: rgba(33, 150, 243, 0.06); }
.benchmark-table tr.benchmark-row-without { background: rgba(255, 193, 7, 0.06); }
.benchmark-table tr.benchmark-row-with:hover { background: rgba(33, 150, 243, 0.12); }
.benchmark-table tr.benchmark-row-without:hover { background: rgba(255, 193, 7, 0.12); }
.benchmark-table tr.benchmark-row-avg { font-weight: 600; border-top: 2px solid var(--border); }
.benchmark-table tr.benchmark-row-avg.benchmark-row-with { background: rgba(33, 150, 243, 0.12); }
.benchmark-table tr.benchmark-row-avg.benchmark-row-without { background: rgba(255, 193, 7, 0.12); }
.benchmark-delta-positive { color: var(--green); font-weight: 600; }
.benchmark-delta-negative { color: var(--red); font-weight: 600; }
.benchmark-notes {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
}
.benchmark-notes h3 {
font-family: 'Poppins', sans-serif;
font-size: 0.875rem;
margin-bottom: 0.75rem;
}
.benchmark-notes ul {
list-style: disc;
padding-left: 1.25rem;
}
.benchmark-notes li {
font-size: 0.8125rem;
line-height: 1.6;
margin-bottom: 0.375rem;
}
.benchmark-empty {
color: var(--text-muted);
font-style: italic;
text-align: center;
padding: 3rem;
}
/* ---- Navigation ---- */
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
border-top: 1px solid var(--border);
background: var(--surface);
flex-shrink: 0;
}
.nav-btn {
font-family: 'Poppins', sans-serif;
padding: 0.5rem 1.25rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
color: var(--text);
transition: all 0.15s;
}
.nav-btn:hover:not(:disabled) {
background: var(--bg);
border-color: var(--text-muted);
}
.nav-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.done-btn {
font-family: 'Poppins', sans-serif;
padding: 0.5rem 1.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
color: var(--text);
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.15s;
}
.done-btn:hover {
background: var(--bg);
border-color: var(--text-muted);
}
.done-btn.ready {
border: none;
background: var(--accent);
color: white;
font-weight: 600;
}
.done-btn.ready:hover {
background: var(--accent-hover);
}
/* ---- Done overlay ---- */
.done-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 100;
justify-content: center;
align-items: center;
}
.done-overlay.visible {
display: flex;
}
.done-card {
background: var(--surface);
border-radius: 12px;
padding: 2rem 3rem;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px;
}
.done-card h2 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.done-card p {
color: var(--text-muted);
margin-bottom: 1.5rem;
line-height: 1.5;
}
.done-card .btn-row {
display: flex;
gap: 0.5rem;
justify-content: center;
}
.done-card button {
padding: 0.5rem 1.25rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
cursor: pointer;
font-size: 0.875rem;
}
.done-card button:hover {
background: var(--bg);
}
/* ---- Toast ---- */
.toast {
position: fixed;
bottom: 5rem;
left: 50%;
transform: translateX(-50%);
background: var(--header-bg);
color: var(--header-text);
padding: 0.625rem 1.25rem;
border-radius: var(--radius);
font-size: 0.875rem;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
z-index: 200;
}
.toast.visible {
opacity: 1;
}
</style>
</head>
<body>
<div id="app" style="height:100vh; display:flex; flex-direction:column;">
<div class="header">
<div>
<h1>Eval Review: <span id="skill-name"></span></h1>
<div class="instructions">Review each output and leave feedback below. Navigate with arrow keys or buttons. When done, copy feedback and paste into Claude Code.</div>
</div>
<div class="progress" id="progress"></div>
</div>
<!-- View tabs (only shown when benchmark data exists) -->
<div class="view-tabs" id="view-tabs" style="display:none;">
<button class="view-tab active" onclick="switchView('outputs')">Outputs</button>
<button class="view-tab" onclick="switchView('benchmark')">Benchmark</button>
</div>
<!-- Outputs panel (qualitative review) -->
<div class="view-panel active" id="panel-outputs">
<div class="main">
<!-- Prompt -->
<div class="section">
<div class="section-header">Prompt <span class="config-badge" id="config-badge" style="display:none;"></span></div>
<div class="section-body">
<div class="prompt-text" id="prompt-text"></div>
</div>
</div>
<!-- Outputs -->
<div class="section">
<div class="section-header">Output</div>
<div class="section-body" id="outputs-body">
<div class="empty-state">No output files found</div>
</div>
</div>
<!-- Previous Output (collapsible) -->
<div class="section" id="prev-outputs-section" style="display:none;">
<div class="section-header">
<div class="grades-toggle" onclick="togglePrevOutputs()">
<span class="arrow" id="prev-outputs-arrow">&#9654;</span>
Previous Output
</div>
</div>
<div class="grades-content" id="prev-outputs-content"></div>
</div>
<!-- Grades (collapsible) -->
<div class="section" id="grades-section" style="display:none;">
<div class="section-header">
<div class="grades-toggle" onclick="toggleGrades()">
<span class="arrow" id="grades-arrow">&#9654;</span>
Formal Grades
</div>
</div>
<div class="grades-content" id="grades-content"></div>
</div>
<!-- Feedback -->
<div class="section">
<div class="section-header">Your Feedback</div>
<div class="section-body">
<textarea
class="feedback-textarea"
id="feedback"
placeholder="What do you think of this output? Any issues, suggestions, or things that look great?"
></textarea>
<div class="feedback-status" id="feedback-status"></div>
<div class="prev-feedback" id="prev-feedback" style="display:none;">
<div class="prev-feedback-label">Previous feedback</div>
<div id="prev-feedback-text"></div>
</div>
</div>
</div>
</div>
<div class="nav" id="outputs-nav">
<button class="nav-btn" id="prev-btn" onclick="navigate(-1)">&#8592; Previous</button>
<button class="done-btn" id="done-btn" onclick="showDoneDialog()">Submit All Reviews</button>
<button class="nav-btn" id="next-btn" onclick="navigate(1)">Next &#8594;</button>
</div>
</div><!-- end panel-outputs -->
<!-- Benchmark panel (quantitative stats) -->
<div class="view-panel" id="panel-benchmark">
<div class="benchmark-view" id="benchmark-content">
<div class="benchmark-empty">No benchmark data available. Run a benchmark to see quantitative results here.</div>
</div>
</div>
</div>
<!-- Done overlay -->
<div class="done-overlay" id="done-overlay">
<div class="done-card">
<h2>Review Complete</h2>
<p>Your feedback has been saved. Go back to your Claude Code session and tell Claude you're done reviewing.</p>
<div class="btn-row">
<button onclick="closeDoneDialog()">OK</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
// ---- Embedded data (injected by generate_review.py) ----
const EMBEDDED_DATA = {"skill_name": "soneta-form-xml", "runs": [{"id": "pageform-z-gridem-with_skill", "prompt": "Utw\u00f3rz plik Zamowienie.Szczegoly.pageform.xml dla platformy enova365. Zak\u0142adka powinna zawiera\u0107:\n- Grup\u0119 'Dane podstawowe' z polami: Numer (szeroko\u015b\u0107 20), DataZlozenia (szeroko\u015b\u0107 15), Kontrahent (szeroko\u015b\u0107 *), Status (szeroko\u015b\u0107 15)\n- Grup\u0119 'Warunki' z uk\u0142adem dwukolumnowym (Row > Stack): lewa kolumna ma SposobDostawy (20) i TerminDostawy (15), prawa kolumna ma SposobZaplaty (20) i TerminZaplaty (15)\n- Grup\u0119 'Pozycje' z Grid (IsToolbarVisible, EditInPlace, NewInPlace) i kolumnami: Lp (5), Towar (*), Ilosc (10) z Footer=Sum, CenaJednostkowa (18), Wartosc (15) z Footer=Sum\nPriority zak\u0142adki: 10.", "eval_id": 1, "outputs": [{"name": "Zamowienie.Szczegoly.pageform.xml", "type": "text", "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<DataForm xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://www.enova.pl/schema/form.xsd\"\n xsi:schemaLocation=\"http://www.enova.pl/schema/ http://www.enova.pl/schema/form.xsd\"\n Priority=\"10\">\n\n <Page CaptionHtml=\"Szczegoly\" DataContext=\"{DataSource}\">\n\n <Group CaptionHtml=\"Dane podstawowe\">\n <Field CaptionHtml=\"Numer\" Width=\"20\" EditValue=\"{Numer}\" />\n <Field CaptionHtml=\"DataZlozenia\" Width=\"15\" EditValue=\"{DataZlozenia}\" />\n <Field CaptionHtml=\"Kontrahent\" Width=\"*\" EditValue=\"{Kontrahent}\" />\n <Field CaptionHtml=\"Status\" Width=\"15\" EditValue=\"{Status}\" />\n </Group>\n\n <Group CaptionHtml=\"Warunki\">\n <Row>\n <Stack>\n <Field CaptionHtml=\"SposobDostawy\" Width=\"20\" EditValue=\"{SposobDostawy}\" />\n <Field CaptionHtml=\"TerminDostawy\" Width=\"15\" EditValue=\"{TerminDostawy}\" />\n </Stack>\n <Stack>\n <Field CaptionHtml=\"SposobZaplaty\" Width=\"20\" EditValue=\"{SposobZaplaty}\" />\n <Field CaptionHtml=\"TerminZaplaty\" Width=\"15\" EditValue=\"{TerminZaplaty}\" />\n </Stack>\n </Row>\n </Group>\n\n <Group CaptionHtml=\"Pozycje\">\n <Grid Width=\"*\" Height=\"*\"\n EditValue=\"{Pozycje}\"\n IsToolbarVisible=\"true\"\n EditInPlace=\"true\"\n NewInPlace=\"true\">\n <Field CaptionHtml=\"Lp\" Width=\"5\" EditValue=\"{Lp}\" />\n <Field CaptionHtml=\"Towar\" Width=\"*\" EditValue=\"{Towar}\" />\n <Field CaptionHtml=\"Ilosc\" Width=\"10\" EditValue=\"{Ilosc}\" Footer=\"Sum\" />\n <Field CaptionHtml=\"CenaJednostkowa\" Width=\"18\" EditValue=\"{CenaJednostkowa}\" />\n <Field CaptionHtml=\"Wartosc\" Width=\"15\" EditValue=\"{Wartosc}\" Footer=\"Sum\" />\n </Grid>\n </Group>\n\n </Page>\n</DataForm>\n"}], "grading": {"expectations": [{"text": "Plik zaczyna si\u0119 od <?xml version=\"1.0\" encoding=\"utf-8\"?>", "passed": true, "evidence": "Linia 1: <?xml version=\"1.0\" encoding=\"utf-8\"?>"}, {"text": "Element DataForm ma atrybut Priority=\"10\"", "passed": true, "evidence": "Linia 6: Priority=\"10\" w elemencie DataForm"}, {"text": "Element Page ma atrybut DataContext=\"{DataSource}\"", "passed": true, "evidence": "Linia 8: <Page CaptionHtml=\"Szczegoly\" DataContext=\"{DataSource}\">"}, {"text": "S\u0105 trzy elementy Group: 'Dane podstawowe', 'Warunki', 'Pozycje'", "passed": true, "evidence": "Linie 10, 17, 30: Group CaptionHtml=\"Dane podstawowe\", \"Warunki\", \"Pozycje\""}, {"text": "Uk\u0142ad dwukolumnowy w grupie Warunki u\u017cywa Row zawieraj\u0105cego Stack", "passed": true, "evidence": "Linie 18-28: <Row><Stack>SposobDostawy, TerminDostawy</Stack><Stack>SposobZaplaty, TerminZaplaty</Stack></Row>"}, {"text": "Element Grid ma atrybuty IsToolbarVisible=\"true\", EditInPlace=\"true\", NewInPlace=\"true\"", "passed": true, "evidence": "Linie 31-35: Grid z IsToolbarVisible=\"true\" EditInPlace=\"true\" NewInPlace=\"true\""}, {"text": "Pola Ilosc i Wartosc w Grid maj\u0105 atrybut Footer=\"Sum\"", "passed": true, "evidence": "Linia 38: Footer=\"Sum\" na Ilosc; Linia 40: Footer=\"Sum\" na Wartosc"}, {"text": "Binding p\u00f3l u\u017cywa sk\u0142adni {NazwaPola} w cudzys\u0142owie", "passed": true, "evidence": "EditValue=\"{Numer}\", EditValue=\"{DataZlozenia}\" itp. \u2014 poprawna sk\u0142adnia"}], "summary": {"passed": 8, "failed": 0, "total": 8, "pass_rate": 1.0}}}, {"id": "pageform-z-gridem-without_skill", "prompt": "Utw\u00f3rz plik Zamowienie.Szczegoly.pageform.xml dla platformy enova365. Zak\u0142adka powinna zawiera\u0107:\n- Grup\u0119 'Dane podstawowe' z polami: Numer (szeroko\u015b\u0107 20), DataZlozenia (szeroko\u015b\u0107 15), Kontrahent (szeroko\u015b\u0107 *), Status (szeroko\u015b\u0107 15)\n- Grup\u0119 'Warunki' z uk\u0142adem dwukolumnowym (Row > Stack): lewa kolumna ma SposobDostawy (20) i TerminDostawy (15), prawa kolumna ma SposobZaplaty (20) i TerminZaplaty (15)\n- Grup\u0119 'Pozycje' z Grid (IsToolbarVisible, EditInPlace, NewInPlace) i kolumnami: Lp (5), Towar (*), Ilosc (10) z Footer=Sum, CenaJednostkowa (18), Wartosc (15) z Footer=Sum\nPriority zak\u0142adki: 10.", "eval_id": 1, "outputs": [{"name": "Zamowienie.Szczegoly.pageform.xml", "type": "text", "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PageForm xmlns=\"http://www.enova.pl/schemas/form\">\n <PageForm.Priority>10</PageForm.Priority>\n <PageForm.Groups>\n <Group Name=\"Dane podstawowe\">\n <Group.Controls>\n <Control Name=\"Numer\" Width=\"20\" />\n <Control Name=\"DataZlozenia\" Width=\"15\" />\n <Control Name=\"Kontrahent\" Width=\"*\" />\n <Control Name=\"Status\" Width=\"15\" />\n </Group.Controls>\n </Group>\n <Group Name=\"Warunki\">\n <Group.Controls>\n <Row>\n <Stack>\n <Control Name=\"SposobDostawy\" Width=\"20\" />\n <Control Name=\"TerminDostawy\" Width=\"15\" />\n </Stack>\n <Stack>\n <Control Name=\"SposobZaplaty\" Width=\"20\" />\n <Control Name=\"TerminZaplaty\" Width=\"15\" />\n </Stack>\n </Row>\n </Group.Controls>\n </Group>\n <Group Name=\"Pozycje\">\n <Group.Controls>\n <Grid IsToolbarVisible=\"true\" EditInPlace=\"true\" NewInPlace=\"true\">\n <Grid.Columns>\n <Column Name=\"Lp\" Width=\"5\" />\n <Column Name=\"Towar\" Width=\"*\" />\n <Column Name=\"Ilosc\" Width=\"10\" Footer=\"Sum\" />\n <Column Name=\"CenaJednostkowa\" Width=\"18\" />\n <Column Name=\"Wartosc\" Width=\"15\" Footer=\"Sum\" />\n </Grid.Columns>\n </Grid>\n </Group.Controls>\n </Group>\n </PageForm.Groups>\n</PageForm>\n"}], "grading": {"expectations": [{"text": "Plik zaczyna si\u0119 od <?xml version=\"1.0\" encoding=\"utf-8\"?>", "passed": true, "evidence": "Linia 1: <?xml version=\"1.0\" encoding=\"utf-8\"?>"}, {"text": "Element DataForm ma atrybut Priority=\"10\"", "passed": false, "evidence": "U\u017cyto <PageForm> zamiast <DataForm>, a Priority jako child element <PageForm.Priority>10</PageForm.Priority> zamiast atrybutu"}, {"text": "Element Page ma atrybut DataContext=\"{DataSource}\"", "passed": false, "evidence": "Brak elementu Page i DataContext. U\u017cyto <PageForm.Groups> zamiast <Page DataContext=...>"}, {"text": "S\u0105 trzy elementy Group: 'Dane podstawowe', 'Warunki', 'Pozycje'", "passed": false, "evidence": "Grupy istniej\u0105 ale z atrybutem Name zamiast CaptionHtml i wewn\u0105trz b\u0142\u0119dnej struktury <PageForm.Groups>"}, {"text": "Uk\u0142ad dwukolumnowy w grupie Warunki u\u017cywa Row zawieraj\u0105cego Stack", "passed": true, "evidence": "Linie 15-25: <Row><Stack>...</Stack><Stack>...</Stack></Row> \u2014 poprawny layout"}, {"text": "Element Grid ma atrybuty IsToolbarVisible=\"true\", EditInPlace=\"true\", NewInPlace=\"true\"", "passed": true, "evidence": "Linia 29: Grid z IsToolbarVisible=\"true\" EditInPlace=\"true\" NewInPlace=\"true\""}, {"text": "Pola Ilosc i Wartosc w Grid maj\u0105 atrybut Footer=\"Sum\"", "passed": true, "evidence": "Linie 33, 35: Footer=\"Sum\" na Column Ilosc i Wartosc"}, {"text": "Binding p\u00f3l u\u017cywa sk\u0142adni {NazwaPola} w cudzys\u0142owie", "passed": false, "evidence": "Brak atrybutu EditValue. U\u017cywa <Control Name=\"Numer\" Width=\"20\" /> bez bindowania danych"}], "summary": {"passed": 4, "failed": 4, "total": 8, "pass_rate": 0.5}}}, {"id": "viewform-lista-with_skill", "prompt": "Stw\u00f3rz plik Pracownicy.viewform.xml dla widoku listy pracownik\u00f3w w enova365. Widok listy powinien zawiera\u0107 kolumny: Kod (8), NazwiskoImie (30), NumerPracownika (20), Dzial (*), Stanowisko (20), DataZatrudnienia (15). Domy\u015blne sortowanie po NazwiskoImie. Dodaj IsToolbarVisible i IsFilterRowVisible. Zachowaj w\u0142a\u015bciw\u0105 struktur\u0119 pliku viewform.xml.", "eval_id": 2, "outputs": [{"name": "Pracownicy.viewform.xml", "type": "text", "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<DataForm xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://www.enova.pl/schema/form.xsd\"\n xsi:schemaLocation=\"http://www.enova.pl/schema/ http://www.enova.pl/schema/form.xsd\"\n ViewType=\"Folder\"\n Mode=\"Folder\">\n\n <Grid Width=\"*\" Height=\"*\"\n IsToolbarVisible=\"true\"\n IsFilterRowVisible=\"true\"\n OrderBy=\"NazwiskoImie\">\n <Field CaptionHtml=\"Kod\" Width=\"8\" EditValue=\"{Kod}\" />\n <Field CaptionHtml=\"Nazwisko i imi\u0119\" Width=\"30\" EditValue=\"{NazwiskoImie}\" />\n <Field CaptionHtml=\"Numer pracownika\" Width=\"20\" EditValue=\"{NumerPracownika}\" />\n <Field CaptionHtml=\"Dzia\u0142\" Width=\"*\" EditValue=\"{Dzial}\" />\n <Field CaptionHtml=\"Stanowisko\" Width=\"20\" EditValue=\"{Stanowisko}\" />\n <Field CaptionHtml=\"Data zatrudnienia\" Width=\"15\" EditValue=\"{DataZatrudnienia}\" />\n </Grid>\n\n</DataForm>\n"}], "grading": {"expectations": [{"text": "Plik ma poprawn\u0105 deklaracj\u0119 XML", "passed": true, "evidence": "Linia 1: <?xml version=\"1.0\" encoding=\"utf-8\"?>"}, {"text": "Element DataForm ma xmlns=\"http://www.enova.pl/schema/form.xsd\"", "passed": true, "evidence": "Linia 5: xmlns=\"http://www.enova.pl/schema/form.xsd\" \u2014 poprawny namespace"}, {"text": "Jest element Grid z EditValue lub bez (dla widoku listy)", "passed": true, "evidence": "Linia 9: <Grid Width=\"*\" Height=\"*\" IsToolbarVisible=\"true\" ...>"}, {"text": "Grid ma atrybut OrderBy=\"NazwiskoImie\"", "passed": true, "evidence": "Linia 12: OrderBy=\"NazwiskoImie\""}, {"text": "Grid ma IsToolbarVisible=\"true\" i IsFilterRowVisible=\"true\"", "passed": true, "evidence": "Linie 10-11: IsToolbarVisible=\"true\" IsFilterRowVisible=\"true\""}, {"text": "S\u0105 kolumny Field dla: Kod, NazwiskoImie, NumerPracownika, Dzial, Stanowisko, DataZatrudnienia", "passed": true, "evidence": "Linie 13-18: 6 element\u00f3w Field z EditValue dla ka\u017cdego z wymienionych p\u00f3l"}, {"text": "Pole Dzial ma Width=\"*\"", "passed": true, "evidence": "Linia 16: <Field CaptionHtml=\"Dzia\u0142\" Width=\"*\" EditValue=\"{Dzial}\" />"}], "summary": {"passed": 7, "failed": 0, "total": 7, "pass_rate": 1.0}}}, {"id": "viewform-lista-without_skill", "prompt": "Stw\u00f3rz plik Pracownicy.viewform.xml dla widoku listy pracownik\u00f3w w enova365. Widok listy powinien zawiera\u0107 kolumny: Kod (8), NazwiskoImie (30), NumerPracownika (20), Dzial (*), Stanowisko (20), DataZatrudnienia (15). Domy\u015blne sortowanie po NazwiskoImie. Dodaj IsToolbarVisible i IsFilterRowVisible. Zachowaj w\u0142a\u015bciw\u0105 struktur\u0119 pliku viewform.xml.", "eval_id": 2, "outputs": [{"name": "Pracownicy.viewform.xml", "type": "text", "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<viewform xmlns=\"http://www.soneta.pl/viewform\">\n <grid name=\"Pracownicy\" defaultsort=\"NazwiskoImie\" istoolbarvisible=\"true\" isfilterrowvisible=\"true\">\n <columns>\n <column name=\"Kod\" width=\"8\" />\n <column name=\"NazwiskoImie\" width=\"30\" />\n <column name=\"NumerPracownika\" width=\"20\" />\n <column name=\"Dzial\" width=\"*\" />\n <column name=\"Stanowisko\" width=\"20\" />\n <column name=\"DataZatrudnienia\" width=\"15\" />\n </columns>\n </grid>\n</viewform>\n"}], "grading": {"expectations": [{"text": "Plik ma poprawn\u0105 deklaracj\u0119 XML", "passed": true, "evidence": "Linia 1: <?xml version=\"1.0\" encoding=\"utf-8\"?>"}, {"text": "Element DataForm ma xmlns=\"http://www.enova.pl/schema/form.xsd\"", "passed": false, "evidence": "U\u017cyto <viewform xmlns=\"http://www.soneta.pl/viewform\"> zamiast <DataForm xmlns=\"http://www.enova.pl/schema/form.xsd\">"}, {"text": "Jest element Grid z EditValue lub bez (dla widoku listy)", "passed": false, "evidence": "U\u017cyto <grid> (lowercase) zamiast <Grid>. Elementy XML s\u0105 case-sensitive \u2014 to b\u0142\u0105d."}, {"text": "Grid ma atrybut OrderBy=\"NazwiskoImie\"", "passed": false, "evidence": "U\u017cyto defaultsort=\"NazwiskoImie\" zamiast OrderBy=\"NazwiskoImie\""}, {"text": "Grid ma IsToolbarVisible=\"true\" i IsFilterRowVisible=\"true\"", "passed": false, "evidence": "U\u017cyto istoolbarvisible=\"true\" isfilterrowvisible=\"true\" (lowercase) zamiast PascalCase"}, {"text": "S\u0105 kolumny Field dla: Kod, NazwiskoImie, NumerPracownika, Dzial, Stanowisko, DataZatrudnienia", "passed": true, "evidence": "6 element\u00f3w <column> z w\u0142a\u015bciwymi nazwami \u2014 poprawne pola, cho\u0107 element to <column> nie <Field>"}, {"text": "Pole Dzial ma Width=\"*\"", "passed": true, "evidence": "Linia 8: <column name=\"Dzial\" width=\"*\" /> \u2014 prawid\u0142owe *"}], "summary": {"passed": 3, "failed": 4, "total": 7, "pass_rate": 0.43}}}, {"id": "warunkowa-widocznosc-with_skill", "prompt": "Poka\u017c jak w pageform.xml enova365 zastosowa\u0107 warunkow\u0105 widoczno\u015b\u0107 p\u00f3l u\u017cywaj\u0105c wyra\u017ce\u0144 RowCondition. Stw\u00f3rz przyk\u0142adowy plik Kontrahent.Kredyt.pageform.xml z nast\u0119puj\u0105cymi wzorcami:\n1. Pole 'LimitKredytu' widoczne tylko gdy TypKlienta='Korporacyjny'\n2. Pole 'WarunkiSpecjalne' widoczne gdy Status='Premium' LUB Status='VIP'\n3. Pole 'OstrzezenieBranze' widoczne gdy NIE Status='Aktywny'\n4. Przycisk Command widoczny gdy ObrotyRoczne > 0 (u\u017cyj WorkCondition lub w\u0142asno\u015b\u0107 bool)\n5. Ca\u0142a Grupa 'Dane zaawansowane' zwijalna (Collapsable) z Renderable uzale\u017cnionym od licencji", "eval_id": 3, "outputs": [{"name": "Kontrahent.Kredyt.pageform.xml", "type": "text", "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Zak\u0142adka: Kredyt kontrahenta\n Plik: Kontrahent.Kredyt.pageform.xml\n\n Demonstruje wzorce warunkowej widoczno\u015bci przy u\u017cyciu:\n - RowCondition ({?...}) dla por\u00f3wna\u0144 warto\u015bci p\u00f3l\n - Visibility z bindowaniem do w\u0142a\u015bciwo\u015bci bool\n - Renderable uzale\u017cniony od licencji (liczony raz przy logowaniu)\n - Class=\"Collapsable\" dla zwijanych grup\n-->\n<DataForm xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n xmlns=\"http://www.enova.pl/schema/form.xsd\"\n xsi:schemaLocation=\"http://www.enova.pl/schema/ http://www.enova.pl/schema/form.xsd\"\n Priority=\"30\">\n\n <Page CaptionHtml=\"Kredyt\" DataContext=\"{DataSource}\">\n\n <!-- =====================================================================\n WZORZEC 1: Pole widoczne tylko gdy TypKlienta = 'Korporacyjny'\n U\u017cywamy wyra\u017cenia RowCondition: {?TypKlienta=Korporacyjny}\n Pole LimitKredytu jest zwijane (Collapsed) gdy warunek nie jest spe\u0142niony.\n ====================================================================== -->\n <Group CaptionHtml=\"Limit kredytu\">\n <Field CaptionHtml=\"Typ klienta\" Width=\"20\" EditValue=\"{TypKlienta}\" />\n <Field CaptionHtml=\"Limit kredytu\"\n Width=\"18\"\n EditValue=\"{LimitKredytu}\"\n Visibility=\"{?TypKlienta=Korporacyjny}\" />\n <Field CaptionHtml=\"Waluta limitu\"\n Width=\"10\"\n EditValue=\"{WalutaLimitu}\"\n Visibility=\"{?TypKlienta=Korporacyjny}\" />\n </Group>\n\n <!-- =====================================================================\n WZORZEC 2: Pole widoczne gdy Status = 'Premium' LUB Status = 'VIP'\n U\u017cywamy operatora 'or' w wyra\u017ceniu RowCondition.\n Sk\u0142adnia: {?Pole=Warto\u015b\u01071 or Pole=Warto\u015b\u01072}\n ====================================================================== -->\n <Group CaptionHtml=\"Warunki specjalne\">\n <Field CaptionHtml=\"Status klienta\" Width=\"20\" EditValue=\"{Status}\" />\n <Field CaptionHtml=\"Warunki specjalne\"\n Width=\"*\"\n Height=\"3\"\n EditValue=\"{WarunkiSpecjalne}\"\n Visibility=\"{?Status=Premium or Status=VIP}\" />\n <Field CaptionHtml=\"Opiekun VIP\"\n Width=\"30\"\n EditValue=\"{OpiekunVIP}\"\n Visibility=\"{?Status=Premium or Status=VIP}\" />\n </Group>\n\n <!-- =====================================================================\n WZORZEC 3: Pole widoczne gdy NIE Status = 'Aktywny'\n U\u017cywamy negacji w wyra\u017ceniu RowCondition: {?!Status=Aktywny}\n Ostrze\u017cenie o bran\u017cy pojawia si\u0119 tylko dla nieaktywnych kontrahent\u00f3w.\n ====================================================================== -->\n <Group CaptionHtml=\"Ostrze\u017cenia\">\n <Field CaptionHtml=\"Ostrzezenie branze\"\n Width=\"*\"\n EditValue=\"{OstrzezenieBranzy}\"\n Class=\"WarningLabel\"\n Visibility=\"{?!Status=Aktywny}\" />\n <Field CaptionHtml=\"Powod nieaktywnosci\"\n Width=\"*\"\n EditValue=\"{PowodNieaktywnosci}\"\n Visibility=\"{?!Status=Aktywny}\" />\n </Group>\n\n <!-- =====================================================================\n WZORZEC 4: Przycisk Command widoczny gdy ObrotyRoczne > 0\n Dla por\u00f3wna\u0144 numerycznych (> < >= <=) RowCondition nie obs\u0142uguje\n operator\u00f3w arytmetycznych bezpo\u015brednio \u2014 zamiast tego:\n a) U\u017cywamy w\u0142a\u015bciwo\u015bci bool z logiki biznesowej: {MaObrotyRoczne}\n (w\u0142a\u015bciwo\u015b\u0107 C# zwracaj\u0105ca: ObrotyRoczne > 0)\n b) Alternatywnie: worker z w\u0142a\u015bciwo\u015bci\u0105 bool: {Workers.KredytWorker.MaObroty}\n ====================================================================== -->\n <Group CaptionHtml=\"Analiza obrotow\">\n <Row>\n <Field CaptionHtml=\"Obroty roczne\" Width=\"18\" EditValue=\"{ObrotyRoczne}\" />\n <Field CaptionHtml=\"Obroty miesieczne\" Width=\"18\" EditValue=\"{ObrotyMiesieczne}\" />\n <Gap Width=\"*\" />\n <!-- Przycisk widoczny gdy ObrotyRoczne > 0 (via w\u0142a\u015bciwo\u015b\u0107 bool MaObrotyRoczne) -->\n <Command CaptionHtml=\"Analiza kredytowa\"\n Width=\"18\"\n MethodName=\"WykonajAnalize\"\n CommandStyle=\"Important\"\n Visibility=\"{MaObrotyRoczne}\" />\n <!-- Alternatywny wzorzec: worker z w\u0142a\u015bciwo\u015bci\u0105 bool -->\n <Command CaptionHtml=\"Raport obrotow\"\n Width=\"16\"\n MethodName=\"GenerujRaport\"\n DataContext=\"{new KredytExtender}\"\n Visibility=\"{Workers.KredytWorker.MaObroty}\" />\n </Row>\n </Group>\n\n <!-- =====================================================================\n WZORZEC 5: Grupa zwijalna (Collapsable) z Renderable uzale\u017cnionym\n od licencji.\n - Class=\"Collapsable\" \u2014 u\u017cytkownik mo\u017ce zwija\u0107/rozwija\u0107 grup\u0119\n - Renderable=\"{Licence.HAN}\" \u2014 renderowana tylko gdy licencja HAN\n aktywna (warto\u015b\u0107 liczona RAZ przy logowaniu operatora, optymalne\n dla warunk\u00f3w \u015brodowiskowych i licencyjnych)\n ====================================================================== -->\n <Group CaptionHtml=\"Dane zaawansowane\"\n Class=\"Collapsable\"\n Renderable=\"{Licence.HAN}\">\n <Row>\n <Stack LabelWidth=\"20\">\n <Field CaptionHtml=\"Scoring kredytowy\" Width=\"10\" EditValue=\"{ScoringKredytowy}\" />\n <Field CaptionHtml=\"Kategoria ryzyka\" Width=\"20\" EditValue=\"{KategoriaRyzyka}\" />\n <Field CaptionHtml=\"Data ostatniej oceny\" Width=\"15\" EditValue=\"{DataOstOceny}\" />\n </Stack>\n <Stack LabelWidth=\"20\">\n <Field CaptionHtml=\"Maksymalna ekspozycja\" Width=\"18\" EditValue=\"{MaksEkspozycja}\" />\n <Field CaptionHtml=\"Ubezpieczenie kredytu\" Width=\"8\" EditValue=\"{UbezpieczenieKredytu}\" />\n <Field CaptionHtml=\"Firma ubezpieczeniowa\" Width=\"25\" EditValue=\"{FirmaUbezpieczeniowa}\"\n Visibility=\"{UbezpieczenieKredytu}\" />\n </Stack>\n </Row>\n <Field CaptionHtml=\"Uwagi analityka\" Width=\"*\" Height=\"3\" EditValue=\"{UwagiAnalityka}\" />\n </Group>\n\n </Page>\n</DataForm>\n"}], "grading": {"expectations": [{"text": "U\u017cyto sk\u0142adni Visibility=\"{?Pole=Warto\u015b\u0107}\" dla warunku r\u00f3wno\u015bci", "passed": true, "evidence": "Linia 30: Visibility=\"{?TypKlienta=Korporacyjny}\" \u2014 poprawna sk\u0142adnia RowCondition"}, {"text": "U\u017cyto sk\u0142adni OR dla wielu warto\u015bci: {?Status=Premium or Status=VIP}", "passed": true, "evidence": "Linia 48: Visibility=\"{?Status=Premium or Status=VIP}\" \u2014 poprawny operator or"}, {"text": "U\u017cyto negacji: {?!Status=Aktywny}", "passed": true, "evidence": "Linia 65: Visibility=\"{?!Status=Aktywny}\" \u2014 poprawna negacja z !"}, {"text": "Klasa Collapsable jest zastosowana do grupy", "passed": true, "evidence": "Linia 109: <Group CaptionHtml=\"Dane zaawansowane\" Class=\"Collapsable\" ...>"}, {"text": "Atrybut Renderable jest u\u017cyty dla warunku licencji", "passed": true, "evidence": "Linia 110: Renderable=\"{Licence.HAN}\" \u2014 licencja HAN jako warunek renderowania"}], "summary": {"passed": 5, "failed": 0, "total": 5, "pass_rate": 1.0}}}, {"id": "warunkowa-widocznosc-without_skill", "prompt": "Poka\u017c jak w pageform.xml enova365 zastosowa\u0107 warunkow\u0105 widoczno\u015b\u0107 p\u00f3l u\u017cywaj\u0105c wyra\u017ce\u0144 RowCondition. Stw\u00f3rz przyk\u0142adowy plik Kontrahent.Kredyt.pageform.xml z nast\u0119puj\u0105cymi wzorcami:\n1. Pole 'LimitKredytu' widoczne tylko gdy TypKlienta='Korporacyjny'\n2. Pole 'WarunkiSpecjalne' widoczne gdy Status='Premium' LUB Status='VIP'\n3. Pole 'OstrzezenieBranze' widoczne gdy NIE Status='Aktywny'\n4. Przycisk Command widoczny gdy ObrotyRoczne > 0 (u\u017cyj WorkCondition lub w\u0142asno\u015b\u0107 bool)\n5. Ca\u0142a Grupa 'Dane zaawansowane' zwijalna (Collapsable) z Renderable uzale\u017cnionym od licencji", "eval_id": 3, "outputs": [{"name": "Kontrahent.Kredyt.pageform.xml", "type": "text", "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Kontrahent.Kredyt.pageform.xml\n Przyk\u0142ad warunkowej widoczno\u015bci p\u00f3l w enova365 za pomoc\u0105 wyra\u017ce\u0144 RowCondition/Renderable.\n Obiekt biznesowy: Kontrahent (modu\u0142 Handel)\n-->\n<Form\n xmlns=\"http://www.enova.pl/schema/Form.xsd\"\n Caption=\"Kredyt kontrahenta\"\n Domain=\"Handel\"\n Class=\"Kontrahent\"\n View=\"Kredyt\">\n\n <!-- ============================================================\n \u0179R\u00d3D\u0141O DANYCH\n ============================================================ -->\n <DataSources>\n <DataSource Name=\"Kontrahent\" Class=\"Kontrahent\" />\n </DataSources>\n\n <!-- ============================================================\n KONTROLKI / LAYOUT\n ============================================================ -->\n <Controls>\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 1. Pole 'LimitKredytu' widoczne tylko gdy TypKlienta = 'Korporacyjny'\n U\u017cycie atrybutu RowCondition z wyra\u017ceniem por\u00f3wnania warto\u015bci enumeracji/stringa.\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <Edit\n Name=\"LimitKredytu\"\n Caption=\"Limit kredytu\"\n Source=\"Kontrahent.LimitKredytu\"\n RowCondition=\"Kontrahent.TypKlienta = 'Korporacyjny'\"\n Visible=\"True\" />\n\n <!--\n Uwaga: atrybut RowCondition przyjmuje wyra\u017cenie ewaluowane przez silnik\n wyra\u017ce\u0144 Soneta (podobne do SQL WHERE). Gdy warunek jest False, wiersz\n (kontrolka) jest ukrywany \u2013 odpowiednik Visible=False ustawianego\n dynamicznie w runtime.\n -->\n\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 2. Pole 'WarunkiSpecjalne' widoczne gdy Status = 'Premium' LUB Status = 'VIP'\n \u0141\u0105czenie warunk\u00f3w operatorem OR (w Soneta: s\u0142owo kluczowe OR lub ||).\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <Edit\n Name=\"WarunkiSpecjalne\"\n Caption=\"Warunki specjalne\"\n Source=\"Kontrahent.WarunkiSpecjalne\"\n RowCondition=\"Kontrahent.Status = 'Premium' OR Kontrahent.Status = 'VIP'\"\n Visible=\"True\" />\n\n <!--\n Alternatywna forma z nawiasami (zalecana dla czytelno\u015bci):\n RowCondition=\"(Kontrahent.Status = 'Premium') OR (Kontrahent.Status = 'VIP')\"\n -->\n\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 3. Pole 'OstrzezenieBranze' widoczne gdy NIE Status = 'Aktywny'\n Negacja warunku \u2013 operator NOT lub <> (r\u00f3\u017cne od).\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <Edit\n Name=\"OstrzezenieBranze\"\n Caption=\"Ostrze\u017cenie bran\u017cy\"\n Source=\"Kontrahent.OstrzezenieBranze\"\n RowCondition=\"NOT (Kontrahent.Status = 'Aktywny')\"\n Visible=\"True\" />\n\n <!--\n R\u00f3wnowa\u017cna posta\u0107 z operatorem nier\u00f3wno\u015bci:\n RowCondition=\"Kontrahent.Status &lt;&gt; 'Aktywny'\"\n W XML znaki < i > musz\u0105 by\u0107 escapowane jako &lt; i &gt;.\n -->\n\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 4. Przycisk Command widoczny gdy ObrotyRoczne > 0\n Dla warunk\u00f3w numerycznych zalecane jest mapowanie na w\u0142a\u015bciwo\u015b\u0107 bool\n po stronie C# (np. MaObroty : bool { get => ObrotyRoczne > 0; })\n i u\u017cycie jej w RowCondition lub WorkCondition.\n\n Wariant A \u2013 bezpo\u015brednie wyra\u017cenie numeryczne w RowCondition:\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <Command\n Name=\"PrzeliczKredyt\"\n Caption=\"Przelicz limit kredytu\"\n Action=\"Kontrahent.PrzeliczKredyt\"\n RowCondition=\"Kontrahent.ObrotyRoczne &gt; 0\" />\n\n <!--\n Wariant B \u2013 u\u017cycie w\u0142a\u015bciwo\u015bci bool z obiektu biznesowego (ZALECANE):\n <Command\n Name=\"PrzeliczKredyt\"\n Caption=\"Przelicz limit kredytu\"\n Action=\"Kontrahent.PrzeliczKredyt\"\n WorkCondition=\"Kontrahent.MaObroty\" />\n\n WorkCondition kontroluje AKTYWNO\u015a\u0106 (enabled) przycisku,\n RowCondition kontroluje WIDOCZNO\u015a\u0106 (visible).\n Mo\u017cna stosowa\u0107 oba jednocze\u015bnie.\n\n Wariant C \u2013 oba atrybuty razem:\n <Command\n Name=\"PrzeliczKredyt\"\n Caption=\"Przelicz limit kredytu\"\n Action=\"Kontrahent.PrzeliczKredyt\"\n RowCondition=\"Kontrahent.ObrotyRoczne &gt; 0\"\n WorkCondition=\"Kontrahent.MaObroty\" />\n -->\n\n\n <!-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 5. Grupa 'Dane zaawansowane' \u2013 zwijalna (Collapsable) z Renderable\n uzale\u017cnionym od licencji.\n\n Collapsable=\"True\" \u2013 pozwala u\u017cytkownikowi zwin\u0105\u0107 grup\u0119 w UI.\n Renderable \u2013 wyra\u017cenie decyduj\u0105ce, czy kontrolka jest w og\u00f3le\n renderowana (silniejsze ni\u017c Visible: nie zajmuje\n miejsca w DOM formularza gdy False).\n Tu: sprawdzamy dost\u0119pno\u015b\u0107 modu\u0142u/licencji\n przez wywo\u0142anie metody pomocniczej lub w\u0142a\u015bciwo\u015b\u0107\n sesji, np. Session.IsModuleAvailable('CRM').\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <Group\n Name=\"DaneZaawansowane\"\n Caption=\"Dane zaawansowane\"\n Collapsable=\"True\"\n Collapsed=\"False\"\n Renderable=\"Session.Features.IsAvailable('Kontrahent.DaneZaawansowane')\">\n\n <!-- Pola wewn\u0105trz grupy zaawansowanej -->\n <Edit\n Name=\"KategoriaRyzyka\"\n Caption=\"Kategoria ryzyka\"\n Source=\"Kontrahent.KategoriaRyzyka\" />\n\n <Edit\n Name=\"SkorKredytowy\"\n Caption=\"Skor kredytowy\"\n Source=\"Kontrahent.SkorKredytowy\" />\n\n <Edit\n Name=\"DataOstatnejWeryfikacji\"\n Caption=\"Data ostatniej weryfikacji\"\n Source=\"Kontrahent.DataOstatnejWeryfikacji\" />\n\n </Group>\n\n <!--\n Uwaga do Renderable:\n \u2013 Gdy Renderable = False, kontrolka NIE jest tworzona w og\u00f3le (brak w\u0119z\u0142a w drzewie UI).\n \u2013 Gdy Visible = False lub RowCondition = False, kontrolka istnieje, ale jest ukryta.\n \u2013 Renderable jest lepsze dla element\u00f3w zale\u017cnych od licencji/uprawnie\u0144,\n poniewa\u017c eliminuje element z drzewa renderowania.\n\n Przyk\u0142adowe wyra\u017cenia Renderable uzale\u017cnione od licencji:\n Renderable=\"Session.IsLicensed('ModulCRM')\"\n Renderable=\"Session.Rights.HasFeature('KredytKorporacyjny')\"\n Renderable=\"App.Configuration.GetBool('pokazDaneZaawansowane')\"\n -->\n\n </Controls>\n\n</Form>\n"}], "grading": {"expectations": [{"text": "U\u017cyto sk\u0142adni Visibility=\"{?Pole=Warto\u015b\u0107}\" dla warunku r\u00f3wno\u015bci", "passed": false, "evidence": "U\u017cyto RowCondition=\"Kontrahent.TypKlienta = 'Korporacyjny'\" (nie istniej\u0105cy atrybut) zamiast Visibility=\"{?...}\""}, {"text": "U\u017cyto sk\u0142adni OR dla wielu warto\u015bci: {?Status=Premium or Status=VIP}", "passed": false, "evidence": "U\u017cyto RowCondition=\"... OR ...\" (SQL-like syntax) zamiast Visibility=\"{?... or ...}\""}, {"text": "U\u017cyto negacji: {?!Status=Aktywny}", "passed": false, "evidence": "U\u017cyto RowCondition=\"NOT (...)\" zamiast Visibility=\"{?!...}\""}, {"text": "Klasa Collapsable jest zastosowana do grupy", "passed": false, "evidence": "U\u017cyto Collapsable=\"True\" jako atrybut zamiast Class=\"Collapsable\""}, {"text": "Atrybut Renderable jest u\u017cyty dla warunku licencji", "passed": false, "evidence": "U\u017cyto Renderable=\"Session.Features.IsAvailable(...)\" \u2014 b\u0142\u0119dna sk\u0142adnia wyra\u017cenia, nie jest to format {Licence.X}"}], "summary": {"passed": 0, "failed": 5, "total": 5, "pass_rate": 0.0}}}], "previous_feedback": {}, "previous_outputs": {}, "benchmark": {"metadata": {"skill_name": "soneta-form-xml", "skill_path": "/Users/marcin/d/Skills/soneta-form-xml", "executor_model": "claude-sonnet-4-6", "timestamp": "2026-03-05T12:00:00Z", "evals_run": ["pageform-z-gridem", "viewform-lista", "warunkowa-widocznosc"], "runs_per_configuration": 1}, "runs": [{"eval_id": 1, "eval_name": "pageform-z-gridem", "configuration": "with_skill", "run_number": 1, "result": {"pass_rate": 1.0, "passed": 8, "failed": 0, "total": 8, "time_seconds": 37.8, "tokens": 32707, "tool_calls": 9, "errors": 0}, "expectations": [{"text": "Plik zaczyna si\u0119 od <?xml...?>", "passed": true, "evidence": "Poprawna deklaracja XML"}, {"text": "DataForm ma Priority=\"10\"", "passed": true, "evidence": "Atrybut w elemencie root"}, {"text": "Page ma DataContext=\"{DataSource}\"", "passed": true, "evidence": "Linia 8"}, {"text": "Trzy grupy: Dane podstawowe, Warunki, Pozycje", "passed": true, "evidence": "Linie 10,17,30"}, {"text": "Uk\u0142ad Row>Stack w grupie Warunki", "passed": true, "evidence": "Linie 18-28"}, {"text": "Grid: IsToolbarVisible, EditInPlace, NewInPlace", "passed": true, "evidence": "Linie 31-35"}, {"text": "Footer=Sum na Ilosc i Wartosc", "passed": true, "evidence": "Linie 38,40"}, {"text": "Binding {NazwaPola}", "passed": true, "evidence": "EditValue we wszystkich polach"}]}, {"eval_id": 1, "eval_name": "pageform-z-gridem", "configuration": "without_skill", "run_number": 1, "result": {"pass_rate": 0.5, "passed": 4, "failed": 4, "total": 8, "time_seconds": 17.7, "tokens": 4263, "tool_calls": 3, "errors": 0}, "expectations": [{"text": "Plik zaczyna si\u0119 od <?xml...?>", "passed": true, "evidence": "Poprawna deklaracja"}, {"text": "DataForm ma Priority=\"10\"", "passed": false, "evidence": "U\u017cywa <PageForm.Priority> zamiast atrybutu"}, {"text": "Page ma DataContext=\"{DataSource}\"", "passed": false, "evidence": "Brak Page i DataContext"}, {"text": "Trzy grupy: Dane podstawowe, Warunki, Pozycje", "passed": false, "evidence": "Grupy z Name= zamiast CaptionHtml="}, {"text": "Uk\u0142ad Row>Stack w grupie Warunki", "passed": true, "evidence": "Row>Stack obecne"}, {"text": "Grid: IsToolbarVisible, EditInPlace, NewInPlace", "passed": true, "evidence": "Atrybuty obecne"}, {"text": "Footer=Sum na Ilosc i Wartosc", "passed": true, "evidence": "Footer=Sum na Column"}, {"text": "Binding {NazwaPola}", "passed": false, "evidence": "Brak EditValue \u2014 u\u017cywa Name= na Control"}]}, {"eval_id": 2, "eval_name": "viewform-lista", "configuration": "with_skill", "run_number": 1, "result": {"pass_rate": 1.0, "passed": 7, "failed": 0, "total": 7, "time_seconds": 38.4, "tokens": 34304, "tool_calls": 10, "errors": 0}, "expectations": [{"text": "Poprawna deklaracja XML", "passed": true, "evidence": "Linia 1"}, {"text": "xmlns='http://www.enova.pl/schema/form.xsd'", "passed": true, "evidence": "Poprawny namespace"}, {"text": "Element Grid", "passed": true, "evidence": "Grid z ViewType=Folder"}, {"text": "OrderBy='NazwiskoImie'", "passed": true, "evidence": "Linia 12"}, {"text": "IsToolbarVisible i IsFilterRowVisible", "passed": true, "evidence": "Linie 10-11"}, {"text": "6 p\u00f3l: Kod, NazwiskoImie, NumerPracownika, Dzial, Stanowisko, DataZatrudnienia", "passed": true, "evidence": "Linie 13-18"}, {"text": "Dzial ma Width='*'", "passed": true, "evidence": "Linia 16"}]}, {"eval_id": 2, "eval_name": "viewform-lista", "configuration": "without_skill", "run_number": 1, "result": {"pass_rate": 0.43, "passed": 3, "failed": 4, "total": 7, "time_seconds": 13.9, "tokens": 3821, "tool_calls": 3, "errors": 0}, "expectations": [{"text": "Poprawna deklaracja XML", "passed": true, "evidence": "Linia 1"}, {"text": "xmlns='http://www.enova.pl/schema/form.xsd'", "passed": false, "evidence": "B\u0142\u0119dny namespace: http://www.soneta.pl/viewform"}, {"text": "Element Grid", "passed": false, "evidence": "<grid> lowercase \u2014 b\u0142\u0119dna nazwa elementu"}, {"text": "OrderBy='NazwiskoImie'", "passed": false, "evidence": "defaultsort= zamiast OrderBy="}, {"text": "IsToolbarVisible i IsFilterRowVisible", "passed": false, "evidence": "Atrybuty lowercase \u2014 b\u0142\u0119dna sk\u0142adnia"}, {"text": "6 p\u00f3l: Kod, NazwiskoImie, NumerPracownika, Dzial, Stanowisko, DataZatrudnienia", "passed": true, "evidence": "<column> z w\u0142a\u015bciwymi nazwami"}, {"text": "Dzial ma Width='*'", "passed": true, "evidence": "width='*' na Dzial"}]}, {"eval_id": 3, "eval_name": "warunkowa-widocznosc", "configuration": "with_skill", "run_number": 1, "result": {"pass_rate": 1.0, "passed": 5, "failed": 0, "total": 5, "time_seconds": 72.7, "tokens": 37262, "tool_calls": 10, "errors": 0}, "expectations": [{"text": "Visibility='{?Pole=Warto\u015b\u0107}'", "passed": true, "evidence": "Linia 30: {?TypKlienta=Korporacyjny}"}, {"text": "OR: {?Status=Premium or Status=VIP}", "passed": true, "evidence": "Linia 48"}, {"text": "Negacja: {?!Status=Aktywny}", "passed": true, "evidence": "Linia 65"}, {"text": "Class='Collapsable' na Group", "passed": true, "evidence": "Linia 109"}, {"text": "Renderable dla licencji", "passed": true, "evidence": "Linia 110: Renderable='{Licence.HAN}'"}]}, {"eval_id": 3, "eval_name": "warunkowa-widocznosc", "configuration": "without_skill", "run_number": 1, "result": {"pass_rate": 0.0, "passed": 0, "failed": 5, "total": 5, "time_seconds": 50.3, "tokens": 5896, "tool_calls": 3, "errors": 0}, "expectations": [{"text": "Visibility='{?Pole=Warto\u015b\u0107}'", "passed": false, "evidence": "RowCondition= (nie istnieje) zamiast Visibility"}, {"text": "OR: {?Status=Premium or Status=VIP}", "passed": false, "evidence": "SQL-like OR syntax zamiast {?... or ...}"}, {"text": "Negacja: {?!Status=Aktywny}", "passed": false, "evidence": "NOT (...) zamiast {?!...}"}, {"text": "Class='Collapsable' na Group", "passed": false, "evidence": "Collapsable='True' atrybut zamiast Class="}, {"text": "Renderable dla licencji", "passed": false, "evidence": "Session.Features.IsAvailable() \u2014 b\u0142\u0119dna sk\u0142adnia"}]}], "run_summary": {"with_skill": {"pass_rate": {"mean": 1.0, "stddev": 0.0, "min": 1.0, "max": 1.0}, "time_seconds": {"mean": 49.6, "stddev": 18.1, "min": 37.8, "max": 72.7}, "tokens": {"mean": 34758, "stddev": 2284, "min": 32707, "max": 37262}}, "without_skill": {"pass_rate": {"mean": 0.31, "stddev": 0.22, "min": 0.0, "max": 0.5}, "time_seconds": {"mean": 27.3, "stddev": 19.1, "min": 13.9, "max": 50.3}, "tokens": {"mean": 4660, "stddev": 1080, "min": 3821, "max": 5896}}, "delta": {"pass_rate": "+0.69", "time_seconds": "+22.3", "tokens": "+30098"}}, "notes": ["Skill daje 100% pass rate we wszystkich 3 ewalach vs 31% bez skilla \u2014 poprawa o 69 punkt\u00f3w procentowych", "Bez skilla model halucynuje nieistniej\u0105ce elementy XML: <PageForm>, <Control>, <viewform>, <Edit>, RowCondition=", "Najs\u0142abszy wynik bez skilla: warunkowa-widocznosc (0%) \u2014 model zna poj\u0119cia ale ma zupe\u0142nie b\u0142\u0119dn\u0105 sk\u0142adni\u0119", "Skill kosztuje wi\u0119cej token\u00f3w (30k vs 4.7k) i czasu (50s vs 27s) \u2014 uzasadnione bior\u0105c pod uwag\u0119 czytanie SKILL.md i ELEMENTS.md", "Asercja 'Poprawna deklaracja XML' zawsze przechodzi \u2014 ma\u0142o dyskryminuj\u0105ca, ale utrzymana dla kompletno\u015bci"]}};
// ---- State ----
let feedbackMap = {}; // run_id -> feedback text
let currentIndex = 0;
let visitedRuns = new Set();
// ---- Init ----
async function init() {
// Load saved feedback from server — but only if this isn't a fresh
// iteration (indicated by previous_feedback being present). When
// previous feedback exists, the feedback.json on disk is stale from
// the prior iteration and should not pre-fill the textareas.
const hasPrevious = Object.keys(EMBEDDED_DATA.previous_feedback || {}).length > 0
|| Object.keys(EMBEDDED_DATA.previous_outputs || {}).length > 0;
if (!hasPrevious) {
try {
const resp = await fetch("/api/feedback");
const data = await resp.json();
if (data.reviews) {
for (const r of data.reviews) feedbackMap[r.run_id] = r.feedback;
}
} catch { /* first run, no feedback yet */ }
}
document.getElementById("skill-name").textContent = EMBEDDED_DATA.skill_name;
showRun(0);
// Wire up feedback auto-save
const textarea = document.getElementById("feedback");
let saveTimeout = null;
textarea.addEventListener("input", () => {
clearTimeout(saveTimeout);
document.getElementById("feedback-status").textContent = "";
saveTimeout = setTimeout(() => saveCurrentFeedback(), 800);
});
}
// ---- Navigation ----
function navigate(delta) {
const newIndex = currentIndex + delta;
if (newIndex >= 0 && newIndex < EMBEDDED_DATA.runs.length) {
saveCurrentFeedback();
showRun(newIndex);
}
}
function updateNavButtons() {
document.getElementById("prev-btn").disabled = currentIndex === 0;
document.getElementById("next-btn").disabled =
currentIndex === EMBEDDED_DATA.runs.length - 1;
}
// ---- Show a run ----
function showRun(index) {
currentIndex = index;
const run = EMBEDDED_DATA.runs[index];
// Progress
document.getElementById("progress").textContent =
`${index + 1} of ${EMBEDDED_DATA.runs.length}`;
// Prompt
document.getElementById("prompt-text").textContent = run.prompt;
// Config badge
const badge = document.getElementById("config-badge");
const configMatch = run.id.match(/(with_skill|without_skill|new_skill|old_skill)/);
if (configMatch) {
const config = configMatch[1];
const isBaseline = config === "without_skill" || config === "old_skill";
badge.textContent = config.replace(/_/g, " ");
badge.className = "config-badge " + (isBaseline ? "config-baseline" : "config-primary");
badge.style.display = "inline-block";
} else {
badge.style.display = "none";
}
// Outputs
renderOutputs(run);
// Previous outputs
renderPrevOutputs(run);
// Grades
renderGrades(run);
// Previous feedback
const prevFb = (EMBEDDED_DATA.previous_feedback || {})[run.id];
const prevEl = document.getElementById("prev-feedback");
if (prevFb) {
document.getElementById("prev-feedback-text").textContent = prevFb;
prevEl.style.display = "block";
} else {
prevEl.style.display = "none";
}
// Feedback
document.getElementById("feedback").value = feedbackMap[run.id] || "";
document.getElementById("feedback-status").textContent = "";
updateNavButtons();
// Track visited runs and promote done button when all visited
visitedRuns.add(index);
const doneBtn = document.getElementById("done-btn");
if (visitedRuns.size >= EMBEDDED_DATA.runs.length) {
doneBtn.classList.add("ready");
}
// Scroll main content to top
document.querySelector(".main").scrollTop = 0;
}
// ---- Render outputs ----
function renderOutputs(run) {
const container = document.getElementById("outputs-body");
container.innerHTML = "";
const outputs = run.outputs || [];
if (outputs.length === 0) {
container.innerHTML = '<div class="empty-state">No output files</div>';
return;
}
for (const file of outputs) {
const fileDiv = document.createElement("div");
fileDiv.className = "output-file";
// Always show file header with download link
const header = document.createElement("div");
header.className = "output-file-header";
const nameSpan = document.createElement("span");
nameSpan.textContent = file.name;
header.appendChild(nameSpan);
const dlBtn = document.createElement("a");
dlBtn.className = "dl-btn";
dlBtn.textContent = "Download";
dlBtn.download = file.name;
dlBtn.href = getDownloadUri(file);
header.appendChild(dlBtn);
fileDiv.appendChild(header);
const content = document.createElement("div");
content.className = "output-file-content";
if (file.type === "text") {
const pre = document.createElement("pre");
pre.textContent = file.content;
content.appendChild(pre);
} else if (file.type === "image") {
const img = document.createElement("img");
img.src = file.data_uri;
img.alt = file.name;
content.appendChild(img);
} else if (file.type === "pdf") {
const iframe = document.createElement("iframe");
iframe.src = file.data_uri;
content.appendChild(iframe);
} else if (file.type === "xlsx") {
renderXlsx(content, file.data_b64);
} else if (file.type === "binary") {
const a = document.createElement("a");
a.className = "download-link";
a.href = file.data_uri;
a.download = file.name;
a.textContent = "Download " + file.name;
content.appendChild(a);
} else if (file.type === "error") {
const pre = document.createElement("pre");
pre.textContent = file.content;
pre.style.color = "var(--red)";
content.appendChild(pre);
}
fileDiv.appendChild(content);
container.appendChild(fileDiv);
}
}
// ---- XLSX rendering via SheetJS ----
function renderXlsx(container, b64Data) {
try {
const raw = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
const wb = XLSX.read(raw, { type: "array" });
for (let i = 0; i < wb.SheetNames.length; i++) {
const sheetName = wb.SheetNames[i];
const ws = wb.Sheets[sheetName];
if (wb.SheetNames.length > 1) {
const sheetLabel = document.createElement("div");
sheetLabel.style.cssText =
"font-weight:600; font-size:0.8rem; color:#b0aea5; margin-top:0.5rem; margin-bottom:0.25rem;";
sheetLabel.textContent = "Sheet: " + sheetName;
container.appendChild(sheetLabel);
}
const htmlStr = XLSX.utils.sheet_to_html(ws, { editable: false });
const wrapper = document.createElement("div");
wrapper.innerHTML = htmlStr;
container.appendChild(wrapper);
}
} catch (err) {
container.textContent = "Error rendering spreadsheet: " + err.message;
}
}
// ---- Grades ----
function renderGrades(run) {
const section = document.getElementById("grades-section");
const content = document.getElementById("grades-content");
if (!run.grading) {
section.style.display = "none";
return;
}
const grading = run.grading;
section.style.display = "block";
// Reset to collapsed
content.classList.remove("open");
document.getElementById("grades-arrow").classList.remove("open");
const summary = grading.summary || {};
const expectations = grading.expectations || [];
let html = '<div style="padding: 1rem;">';
// Summary line
const passRate = summary.pass_rate != null
? Math.round(summary.pass_rate * 100) + "%"
: "?";
const badgeClass = summary.pass_rate >= 0.8 ? "grade-pass" : summary.pass_rate >= 0.5 ? "" : "grade-fail";
html += '<div class="grades-summary">';
html += '<span class="grade-badge ' + badgeClass + '">' + passRate + '</span>';
html += '<span>' + (summary.passed || 0) + ' passed, ' + (summary.failed || 0) + ' failed of ' + (summary.total || 0) + '</span>';
html += '</div>';
// Assertions list
html += '<ul class="assertion-list">';
for (const exp of expectations) {
const statusClass = exp.passed ? "pass" : "fail";
const statusIcon = exp.passed ? "\u2713" : "\u2717";
html += '<li class="assertion-item">';
html += '<span class="assertion-status ' + statusClass + '">' + statusIcon + '</span>';
html += '<span>' + escapeHtml(exp.text) + '</span>';
if (exp.evidence) {
html += '<div class="assertion-evidence">' + escapeHtml(exp.evidence) + '</div>';
}
html += '</li>';
}
html += '</ul>';
html += '</div>';
content.innerHTML = html;
}
function toggleGrades() {
const content = document.getElementById("grades-content");
const arrow = document.getElementById("grades-arrow");
content.classList.toggle("open");
arrow.classList.toggle("open");
}
// ---- Previous outputs (collapsible) ----
function renderPrevOutputs(run) {
const section = document.getElementById("prev-outputs-section");
const content = document.getElementById("prev-outputs-content");
const prevOutputs = (EMBEDDED_DATA.previous_outputs || {})[run.id];
if (!prevOutputs || prevOutputs.length === 0) {
section.style.display = "none";
return;
}
section.style.display = "block";
// Reset to collapsed
content.classList.remove("open");
document.getElementById("prev-outputs-arrow").classList.remove("open");
// Render the files into the content area
content.innerHTML = "";
const wrapper = document.createElement("div");
wrapper.style.padding = "1rem";
for (const file of prevOutputs) {
const fileDiv = document.createElement("div");
fileDiv.className = "output-file";
const header = document.createElement("div");
header.className = "output-file-header";
const nameSpan = document.createElement("span");
nameSpan.textContent = file.name;
header.appendChild(nameSpan);
const dlBtn = document.createElement("a");
dlBtn.className = "dl-btn";
dlBtn.textContent = "Download";
dlBtn.download = file.name;
dlBtn.href = getDownloadUri(file);
header.appendChild(dlBtn);
fileDiv.appendChild(header);
const fc = document.createElement("div");
fc.className = "output-file-content";
if (file.type === "text") {
const pre = document.createElement("pre");
pre.textContent = file.content;
fc.appendChild(pre);
} else if (file.type === "image") {
const img = document.createElement("img");
img.src = file.data_uri;
img.alt = file.name;
fc.appendChild(img);
} else if (file.type === "pdf") {
const iframe = document.createElement("iframe");
iframe.src = file.data_uri;
fc.appendChild(iframe);
} else if (file.type === "xlsx") {
renderXlsx(fc, file.data_b64);
} else if (file.type === "binary") {
const a = document.createElement("a");
a.className = "download-link";
a.href = file.data_uri;
a.download = file.name;
a.textContent = "Download " + file.name;
fc.appendChild(a);
}
fileDiv.appendChild(fc);
wrapper.appendChild(fileDiv);
}
content.appendChild(wrapper);
}
function togglePrevOutputs() {
const content = document.getElementById("prev-outputs-content");
const arrow = document.getElementById("prev-outputs-arrow");
content.classList.toggle("open");
arrow.classList.toggle("open");
}
// ---- Feedback (saved to server -> feedback.json) ----
function saveCurrentFeedback() {
const run = EMBEDDED_DATA.runs[currentIndex];
const text = document.getElementById("feedback").value;
if (text.trim() === "") {
delete feedbackMap[run.id];
} else {
feedbackMap[run.id] = text;
}
// Build reviews array from map
const reviews = [];
for (const [run_id, feedback] of Object.entries(feedbackMap)) {
if (feedback.trim()) {
reviews.push({ run_id, feedback, timestamp: new Date().toISOString() });
}
}
fetch("/api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ reviews, status: "in_progress" }),
}).then(() => {
document.getElementById("feedback-status").textContent = "Saved";
}).catch(() => {
// Static mode or server unavailable — no-op on auto-save,
// feedback will be downloaded on final submit
document.getElementById("feedback-status").textContent = "Will download on submit";
});
}
// ---- Done ----
function showDoneDialog() {
// Save current textarea to feedbackMap (but don't POST yet)
const run = EMBEDDED_DATA.runs[currentIndex];
const text = document.getElementById("feedback").value;
if (text.trim() === "") {
delete feedbackMap[run.id];
} else {
feedbackMap[run.id] = text;
}
// POST once with status: complete — include ALL runs so the model
// can distinguish "no feedback" (looks good) from "not reviewed"
const reviews = [];
const ts = new Date().toISOString();
for (const r of EMBEDDED_DATA.runs) {
reviews.push({ run_id: r.id, feedback: feedbackMap[r.id] || "", timestamp: ts });
}
const payload = JSON.stringify({ reviews, status: "complete" }, null, 2);
fetch("/api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload,
}).then(() => {
document.getElementById("done-overlay").classList.add("visible");
}).catch(() => {
// Server not available (static mode) — download as file
const blob = new Blob([payload], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "feedback.json";
a.click();
URL.revokeObjectURL(url);
document.getElementById("done-overlay").classList.add("visible");
});
}
function closeDoneDialog() {
// Reset status back to in_progress
saveCurrentFeedback();
document.getElementById("done-overlay").classList.remove("visible");
}
// ---- Toast ----
function showToast(message) {
const toast = document.getElementById("toast");
toast.textContent = message;
toast.classList.add("visible");
setTimeout(() => toast.classList.remove("visible"), 2000);
}
// ---- Keyboard nav ----
document.addEventListener("keydown", (e) => {
// Don't capture when typing in textarea
if (e.target.tagName === "TEXTAREA") return;
if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
e.preventDefault();
navigate(-1);
} else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
e.preventDefault();
navigate(1);
}
});
// ---- Util ----
function getDownloadUri(file) {
if (file.data_uri) return file.data_uri;
if (file.data_b64) return "data:application/octet-stream;base64," + file.data_b64;
if (file.type === "text") return "data:text/plain;charset=utf-8," + encodeURIComponent(file.content);
return "#";
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
// ---- View switching ----
function switchView(view) {
document.querySelectorAll(".view-tab").forEach(t => t.classList.remove("active"));
document.querySelectorAll(".view-panel").forEach(p => p.classList.remove("active"));
document.querySelector(`[onclick="switchView('${view}')"]`).classList.add("active");
document.getElementById("panel-" + view).classList.add("active");
}
// ---- Benchmark rendering ----
function renderBenchmark() {
const data = EMBEDDED_DATA.benchmark;
if (!data) return;
// Show the tabs
document.getElementById("view-tabs").style.display = "flex";
const container = document.getElementById("benchmark-content");
const summary = data.run_summary || {};
const metadata = data.metadata || {};
const notes = data.notes || [];
let html = "";
// Header
html += "<h2 style='font-family: Poppins, sans-serif; margin-bottom: 0.5rem;'>Benchmark Results</h2>";
html += "<p style='color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.25rem;'>";
if (metadata.skill_name) html += "<strong>" + escapeHtml(metadata.skill_name) + "</strong> &mdash; ";
if (metadata.timestamp) html += metadata.timestamp + " &mdash; ";
if (metadata.evals_run) html += "Evals: " + metadata.evals_run.join(", ") + " &mdash; ";
html += (metadata.runs_per_configuration || "?") + " runs per configuration";
html += "</p>";
// Summary table
html += '<table class="benchmark-table">';
function fmtStat(stat, pct) {
if (!stat) return "—";
const suffix = pct ? "%" : "";
const m = pct ? (stat.mean * 100).toFixed(0) : stat.mean.toFixed(1);
const s = pct ? (stat.stddev * 100).toFixed(0) : stat.stddev.toFixed(1);
return m + suffix + " ± " + s + suffix;
}
function deltaClass(val) {
if (!val) return "";
const n = parseFloat(val);
if (n > 0) return "benchmark-delta-positive";
if (n < 0) return "benchmark-delta-negative";
return "";
}
// Discover config names dynamically (everything except "delta")
const configs = Object.keys(summary).filter(k => k !== "delta");
const configA = configs[0] || "config_a";
const configB = configs[1] || "config_b";
const labelA = configA.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
const labelB = configB.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
const a = summary[configA] || {};
const b = summary[configB] || {};
const delta = summary.delta || {};
html += "<thead><tr><th>Metric</th><th>" + escapeHtml(labelA) + "</th><th>" + escapeHtml(labelB) + "</th><th>Delta</th></tr></thead>";
html += "<tbody>";
html += "<tr><td><strong>Pass Rate</strong></td>";
html += "<td>" + fmtStat(a.pass_rate, true) + "</td>";
html += "<td>" + fmtStat(b.pass_rate, true) + "</td>";
html += '<td class="' + deltaClass(delta.pass_rate) + '">' + (delta.pass_rate || "—") + "</td></tr>";
// Time (only show row if data exists)
if (a.time_seconds || b.time_seconds) {
html += "<tr><td><strong>Time (s)</strong></td>";
html += "<td>" + fmtStat(a.time_seconds, false) + "</td>";
html += "<td>" + fmtStat(b.time_seconds, false) + "</td>";
html += '<td class="' + deltaClass(delta.time_seconds) + '">' + (delta.time_seconds ? delta.time_seconds + "s" : "—") + "</td></tr>";
}
// Tokens (only show row if data exists)
if (a.tokens || b.tokens) {
html += "<tr><td><strong>Tokens</strong></td>";
html += "<td>" + fmtStat(a.tokens, false) + "</td>";
html += "<td>" + fmtStat(b.tokens, false) + "</td>";
html += '<td class="' + deltaClass(delta.tokens) + '">' + (delta.tokens || "—") + "</td></tr>";
}
html += "</tbody></table>";
// Per-eval breakdown (if runs data available)
const runs = data.runs || [];
if (runs.length > 0) {
const evalIds = [...new Set(runs.map(r => r.eval_id))].sort((a, b) => a - b);
html += "<h3 style='font-family: Poppins, sans-serif; margin-bottom: 0.75rem;'>Per-Eval Breakdown</h3>";
const hasTime = runs.some(r => r.result && r.result.time_seconds != null);
const hasErrors = runs.some(r => r.result && r.result.errors > 0);
for (const evalId of evalIds) {
const evalRuns = runs.filter(r => r.eval_id === evalId);
const evalName = evalRuns[0] && evalRuns[0].eval_name ? evalRuns[0].eval_name : "Eval " + evalId;
html += "<h4 style='font-family: Poppins, sans-serif; margin: 1rem 0 0.5rem; color: var(--text);'>" + escapeHtml(evalName) + "</h4>";
html += '<table class="benchmark-table">';
html += "<thead><tr><th>Config</th><th>Run</th><th>Pass Rate</th>";
if (hasTime) html += "<th>Time (s)</th>";
if (hasErrors) html += "<th>Crashes During Execution</th>";
html += "</tr></thead>";
html += "<tbody>";
// Group by config and render with average rows
const configGroups = [...new Set(evalRuns.map(r => r.configuration))];
for (let ci = 0; ci < configGroups.length; ci++) {
const config = configGroups[ci];
const configRuns = evalRuns.filter(r => r.configuration === config);
if (configRuns.length === 0) continue;
const rowClass = ci === 0 ? "benchmark-row-with" : "benchmark-row-without";
const configLabel = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
for (const run of configRuns) {
const r = run.result || {};
const prClass = r.pass_rate >= 0.8 ? "benchmark-delta-positive" : r.pass_rate < 0.5 ? "benchmark-delta-negative" : "";
html += '<tr class="' + rowClass + '">';
html += "<td>" + configLabel + "</td>";
html += "<td>" + run.run_number + "</td>";
html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + (r.passed || 0) + "/" + (r.total || 0) + ")</td>";
if (hasTime) html += "<td>" + (r.time_seconds != null ? r.time_seconds.toFixed(1) : "—") + "</td>";
if (hasErrors) html += "<td>" + (r.errors || 0) + "</td>";
html += "</tr>";
}
// Average row
const rates = configRuns.map(r => (r.result || {}).pass_rate || 0);
const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
const avgPrClass = avgRate >= 0.8 ? "benchmark-delta-positive" : avgRate < 0.5 ? "benchmark-delta-negative" : "";
html += '<tr class="benchmark-row-avg ' + rowClass + '">';
html += "<td>" + configLabel + "</td>";
html += "<td>Avg</td>";
html += '<td class="' + avgPrClass + '">' + (avgRate * 100).toFixed(0) + "%</td>";
if (hasTime) {
const times = configRuns.map(r => (r.result || {}).time_seconds).filter(t => t != null);
html += "<td>" + (times.length ? (times.reduce((a, b) => a + b, 0) / times.length).toFixed(1) : "—") + "</td>";
}
if (hasErrors) html += "<td></td>";
html += "</tr>";
}
html += "</tbody></table>";
// Per-assertion detail for this eval
const runsWithExpectations = {};
for (const config of configGroups) {
runsWithExpectations[config] = evalRuns.filter(r => r.configuration === config && r.expectations && r.expectations.length > 0);
}
const hasAnyExpectations = Object.values(runsWithExpectations).some(runs => runs.length > 0);
if (hasAnyExpectations) {
// Collect all unique assertion texts across all configs
const allAssertions = [];
const seen = new Set();
for (const config of configGroups) {
for (const run of runsWithExpectations[config]) {
for (const exp of (run.expectations || [])) {
if (!seen.has(exp.text)) {
seen.add(exp.text);
allAssertions.push(exp.text);
}
}
}
}
html += '<table class="benchmark-table" style="margin-top: 0.5rem;">';
html += "<thead><tr><th>Assertion</th>";
for (const config of configGroups) {
const label = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
html += "<th>" + escapeHtml(label) + "</th>";
}
html += "</tr></thead><tbody>";
for (const assertionText of allAssertions) {
html += "<tr><td>" + escapeHtml(assertionText) + "</td>";
for (const config of configGroups) {
html += "<td>";
for (const run of runsWithExpectations[config]) {
const exp = (run.expectations || []).find(e => e.text === assertionText);
if (exp) {
const cls = exp.passed ? "benchmark-delta-positive" : "benchmark-delta-negative";
const icon = exp.passed ? "\u2713" : "\u2717";
html += '<span class="' + cls + '" title="Run ' + run.run_number + ': ' + escapeHtml(exp.evidence || "") + '">' + icon + "</span> ";
} else {
html += "— ";
}
}
html += "</td>";
}
html += "</tr>";
}
html += "</tbody></table>";
}
}
}
// Notes
if (notes.length > 0) {
html += '<div class="benchmark-notes">';
html += "<h3>Analysis Notes</h3>";
html += "<ul>";
for (const note of notes) {
html += "<li>" + escapeHtml(note) + "</li>";
}
html += "</ul></div>";
}
container.innerHTML = html;
}
// ---- Start ----
init();
renderBenchmark();
</script>
</body>
</html>