← Files VeraARCHIVED FILE
modules/report-builder/assets/report-builder-review-widget.html
153 KB · Oct 2, 2026 · 00:29 UTC
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Build Report Review</title>
<style>
:root {
color-scheme: light;
--paper: #ffffff;
--surface: #ffffff;
--soft: #fafafa;
--soft-strong: #f3f4f2;
--ink: #1f2421;
--text: #3f4541;
--muted: #727972;
--border: #e8eae7;
--border-strong: #d8dcd7;
--accent: #35463f;
--accent-strong: #202a26;
--accent-soft: #f3f6f4;
--danger: #7b4b44;
--danger-bg: #fbf7f6;
--warning: #6f6247;
--warning-bg: #fbfaf6;
--ok: #4b6759;
--ok-bg: #f7faf8;
--info: #52646e;
--info-bg: #f7fafb;
--radius: 8px;
--content: 1240px;
}
* { box-sizing: border-box; }
html, body { margin: 0; max-width: 100%; background: var(--paper); }
body {
color: var(--text);
font-family: "Aptos", "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 1rem;
line-height: 1.5;
letter-spacing: 0;
font-kerning: normal;
text-rendering: optimizeLegibility;
}
button, input, textarea { font: inherit; letter-spacing: 0; }
button, input, textarea, select { max-width: 100%; }
.app { min-height: 100vh; padding: 1.5rem; background: var(--paper); }
.shell { width: 100%; max-width: var(--content); min-width: 0; margin: 0 auto; }
.topbar {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
padding-bottom: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border);
}
h1 { margin: 0 0 0.2rem; color: var(--ink); font-size: 1.25rem; font-weight: 650; line-height: 1.18; }
.subtitle { margin: 0; color: var(--muted); font-size: 0.9rem; }
.top-actions, .context-list, .tabs, .action-buttons, .empty-actions {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
min-width: 0;
}
.top-actions, .context-list, .tabs, .action-buttons { justify-content: flex-end; }
.pill, .context-token, .status-token {
display: inline-flex;
align-items: center;
min-width: 0;
max-width: 100%;
min-height: 1.7rem;
border: 1px solid var(--border);
border-radius: 999px;
padding: 0.2rem 0.55rem;
background: var(--surface);
color: var(--muted);
font-size: 0.74rem;
font-weight: 600;
line-height: 1.2;
white-space: normal;
overflow-wrap: anywhere;
}
.summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(8.8rem, 1fr));
gap: 0;
margin-bottom: 1.1rem;
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
}
.metric {
min-width: 0;
min-height: 3.9rem;
border: 0;
border-right: 1px solid var(--border);
border-radius: 0;
padding: 0.72rem 0.9rem 0.68rem 0;
background: transparent;
}
.metric:last-child { border-right: 0; }
.metric span { display: block; color: var(--muted); font-size: 0.74rem; font-weight: 560; }
.metric strong {
display: block;
margin-top: 0.16rem;
color: var(--ink);
font-size: 1.2rem;
font-weight: 650;
line-height: 1.15;
font-variant-numeric: tabular-nums;
}
.run-context, .review-actions {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.75rem;
align-items: center;
border: 0;
border-bottom: 1px solid var(--border);
border-radius: 0;
padding: 0.78rem 0;
margin-bottom: 1rem;
background: transparent;
}
.run-context strong, .progress-meter strong {
display: block;
color: var(--ink);
font-weight: 650;
line-height: 1.25;
}
.run-context span, .progress-meter span {
color: var(--muted);
font-size: 0.84rem;
line-height: 1.35;
overflow-wrap: anywhere;
}
.review-store {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: 0;
border-top: 1px solid var(--border);
padding-top: 0.62rem;
margin-top: 0.2rem;
}
.review-store__item {
min-width: 0;
border-right: 1px solid var(--border);
padding: 0.16rem 0.7rem 0.12rem 0;
}
.review-store__item:last-child { border-right: 0; }
.review-store__item span {
display: block;
color: var(--muted);
font-size: 0.72rem;
font-weight: 620;
line-height: 1.2;
}
.review-store__item strong {
display: block;
margin-top: 0.12rem;
color: var(--ink);
font-size: 0.86rem;
font-weight: 650;
line-height: 1.25;
overflow-wrap: anywhere;
font-variant-numeric: tabular-nums;
}
.review-store__item.is-dirty strong { color: var(--warning); }
.review-store__item.is-applied strong { color: var(--ok); }
.review-store__item.is-empty strong { color: var(--muted); }
.recovery-panel {
grid-column: 1 / -1;
display: none;
grid-template-columns: minmax(9rem, 0.22fr) minmax(0, 1fr);
gap: 0.6rem 0.9rem;
align-items: start;
border-top: 1px solid var(--border);
padding-top: 0.68rem;
color: var(--text);
}
.recovery-panel.is-visible { display: grid; }
.recovery-panel.is-error { color: var(--danger); }
.recovery-panel__title {
color: var(--ink);
font-size: 0.82rem;
font-weight: 650;
line-height: 1.25;
}
.recovery-panel.is-error .recovery-panel__title { color: var(--danger); }
.recovery-panel__body {
display: grid;
gap: 0.35rem;
min-width: 0;
color: var(--muted);
font-size: 0.82rem;
line-height: 1.35;
}
.recovery-panel__line {
display: grid;
grid-template-columns: minmax(5.5rem, 0.18fr) minmax(0, 1fr);
gap: 0.45rem;
min-width: 0;
}
.recovery-panel__line span:first-child {
color: var(--ink);
font-weight: 620;
}
.recovery-panel__line span:last-child {
min-width: 0;
overflow-wrap: anywhere;
}
.data-posture {
display: grid;
grid-template-columns: minmax(7.5rem, auto) minmax(0, 1fr);
gap: 0.75rem;
align-items: start;
border-bottom: 1px solid var(--border);
padding: 0 0 0.9rem;
margin-bottom: 1rem;
}
.data-posture__title {
display: grid;
gap: 0.28rem;
color: var(--ink);
font-size: 0.82rem;
font-weight: 650;
line-height: 1.25;
}
.data-posture__title small {
max-width: 32ch;
color: var(--muted);
font-size: 0.7rem;
font-weight: 450;
line-height: 1.35;
}
.data-posture__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(8.2rem, 1fr));
gap: 0.45rem;
}
.data-posture__item {
min-width: 0;
border-left: 2px solid var(--border);
padding-left: 0.55rem;
}
.data-posture__item span {
display: block;
color: var(--muted);
font-size: 0.7rem;
font-weight: 620;
line-height: 1.2;
}
.data-posture__item strong {
display: block;
margin-top: 0.12rem;
color: var(--text);
font-size: 0.82rem;
font-weight: 590;
line-height: 1.25;
overflow-wrap: anywhere;
}
.execution-provenance {
display: none;
border-bottom: 1px solid var(--border);
padding: 0 0 0.9rem;
margin: -0.2rem 0 1rem;
}
.execution-provenance.is-visible { display: grid; gap: 0.55rem; }
.execution-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.execution-header h2 {
margin: 0;
color: var(--ink);
font-size: 0.82rem;
font-weight: 650;
line-height: 1.25;
}
.execution-header span {
color: var(--muted);
font-size: 0.74rem;
font-weight: 620;
}
.execution-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
gap: 0.5rem;
}
.execution-step {
min-width: 0;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.68rem 0.75rem;
background: var(--surface);
}
.execution-step__title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
color: var(--ink);
font-size: 0.84rem;
font-weight: 650;
line-height: 1.25;
}
.execution-badge {
flex: 0 0 auto;
border: 1px solid var(--border);
border-radius: 999px;
padding: 0.1rem 0.42rem;
color: var(--muted);
font-size: 0.68rem;
font-weight: 650;
}
.execution-badge.is-remote {
border-color: rgba(176, 77, 67, 0.35);
color: var(--danger);
background: rgba(176, 77, 67, 0.08);
}
.execution-kv {
display: grid;
grid-template-columns: minmax(4.8rem, auto) minmax(0, 1fr);
gap: 0.28rem 0.5rem;
margin-top: 0.5rem;
color: var(--text);
font-size: 0.76rem;
line-height: 1.3;
}
.execution-kv span:nth-child(odd) {
color: var(--muted);
font-weight: 620;
}
.execution-kv span:nth-child(even) {
min-width: 0;
overflow-wrap: anywhere;
}
.safeguards {
display: none;
border-bottom: 1px solid var(--border);
padding: 0 0 0.9rem;
margin: -0.2rem 0 1rem;
}
.safeguards.is-visible { display: grid; gap: 0.55rem; }
.safeguards__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.safeguards__header h2 {
margin: 0;
color: var(--ink);
font-size: 0.82rem;
font-weight: 650;
line-height: 1.25;
}
.safeguards__header span {
color: var(--muted);
font-size: 0.74rem;
font-weight: 620;
}
.safeguards__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
gap: 0.45rem;
}
.safeguard {
min-width: 0;
border: 1px solid var(--border);
border-left-width: 3px;
border-radius: var(--radius);
padding: 0.62rem 0.7rem;
background: var(--surface);
}
.safeguard.is-ok { border-left-color: var(--ok); }
.safeguard.is-check { border-left-color: var(--info); }
.safeguard.is-warning { border-left-color: var(--warning); }
.safeguard.is-danger { border-left-color: var(--danger); }
.safeguard__top {
display: flex;
justify-content: space-between;
gap: 0.5rem;
align-items: start;
}
.safeguard__top strong {
min-width: 0;
color: var(--ink);
font-size: 0.8rem;
font-weight: 650;
line-height: 1.25;
overflow-wrap: anywhere;
}
.safeguard__badge {
flex: 0 0 auto;
border: 1px solid var(--border);
border-radius: 999px;
padding: 0.1rem 0.38rem;
color: var(--muted);
font-size: 0.66rem;
font-weight: 650;
line-height: 1.15;
}
.safeguard.is-ok .safeguard__badge { color: var(--ok); }
.safeguard.is-warning .safeguard__badge,
.safeguard.is-danger .safeguard__badge { color: var(--danger); }
.safeguard p {
margin: 0.28rem 0 0;
color: var(--muted);
font-size: 0.76rem;
line-height: 1.3;
overflow-wrap: anywhere;
}
.progress-meter {
display: grid;
grid-template-columns: minmax(8rem, auto) minmax(7rem, 1fr) minmax(0, auto);
gap: 0.45rem 0.7rem;
align-items: center;
min-width: 0;
}
.progress-rail {
position: relative;
width: 100%;
min-width: 7rem;
height: 0.28rem;
border-radius: 999px;
background: var(--border);
overflow: hidden;
}
.progress-rail span {
display: block;
width: 0%;
height: 100%;
border-radius: inherit;
background: var(--accent);
transition: width 160ms ease;
}
.state-strip {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.8rem;
align-items: center;
border-bottom: 1px solid var(--border);
padding: 0 0 0.9rem;
margin-bottom: 0.85rem;
}
.state-filters {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
min-width: 0;
}
.state-chip {
display: inline-grid;
grid-template-columns: auto auto;
gap: 0.38rem;
align-items: center;
min-height: 2rem;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.32rem 0.52rem;
background: var(--surface);
color: var(--text);
cursor: pointer;
font-size: 0.78rem;
line-height: 1.2;
}
.state-chip:hover { background: var(--soft); }
.state-chip[aria-pressed="true"] {
border-color: var(--border-strong);
background: var(--accent-soft);
color: var(--accent-strong);
font-weight: 620;
}
.state-chip strong {
color: var(--ink);
font-size: 0.82rem;
font-variant-numeric: tabular-nums;
}
.state-final {
display: grid;
gap: 0.08rem;
min-width: 8rem;
border-left: 1px solid var(--border);
padding-left: 0.78rem;
}
.state-final span {
color: var(--muted);
font-size: 0.7rem;
font-weight: 620;
line-height: 1.2;
}
.state-final strong {
color: var(--ink);
font-size: 0.86rem;
font-weight: 650;
line-height: 1.2;
}
.state-final.is-blocked strong, .state-final.is-partial_review_applied strong { color: var(--danger); }
.state-final.is-final_ready strong { color: var(--ok); }
.button, .tab, .decision-choice {
max-width: 100%;
min-width: 0;
min-height: 2.15rem;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.42rem 0.64rem;
background: var(--surface);
color: var(--text);
cursor: pointer;
font-size: 0.86rem;
line-height: 1.2;
overflow-wrap: anywhere;
}
.button:hover, .tab:hover, .decision-choice:hover { background: var(--soft); }
.button:disabled { cursor: not-allowed; opacity: 0.52; }
.button.primary { border-color: var(--accent); background: var(--accent); color: #fff; font-weight: 620; }
.button.primary:hover { background: var(--accent-strong); }
.status-message { min-width: 0; color: var(--muted); overflow-wrap: anywhere; }
.status-message.is-error { color: var(--danger); }
.status-message.is-ok { color: var(--ok); }
.toolbar {
display: grid;
grid-template-columns: minmax(14rem, 1fr) auto;
gap: 0.65rem;
align-items: start;
margin-bottom: 0.85rem;
}
.search {
width: 100%;
min-height: 2.45rem;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.54rem 0.7rem;
background: var(--surface);
color: var(--ink);
outline: none;
}
.search::placeholder { color: var(--muted); }
.tab[aria-pressed="true"], .decision-choice[aria-pressed="true"] {
border-color: var(--border-strong);
background: var(--accent-soft);
color: var(--accent-strong);
font-weight: 600;
}
.content {
display: grid;
grid-template-columns: minmax(20rem, 0.78fr) minmax(0, 1.22fr);
gap: 0.85rem;
align-items: start;
}
.panel {
min-width: 0;
overflow: hidden;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
min-height: 2.8rem;
border-bottom: 1px solid var(--border);
padding: 0.68rem 0.85rem;
background: var(--surface);
color: var(--muted);
font-size: 0.84rem;
}
.panel-header h2 {
margin: 0;
color: var(--ink);
font-size: 0.9rem;
font-weight: 650;
line-height: 1.2;
}
.rows { max-height: min(720px, calc(100vh - 22rem)); overflow: auto; }
.row {
display: grid;
grid-template-columns: minmax(6.75rem, 0.28fr) minmax(0, 1fr) minmax(7rem, 9rem);
gap: 0.75rem;
width: 100%;
min-height: 4.15rem;
border: 0;
border-left: 2px solid transparent;
border-bottom: 1px solid var(--border);
padding: 0.7rem 0.82rem 0.7rem calc(0.82rem - 2px);
background: var(--surface);
color: var(--text);
text-align: left;
cursor: pointer;
}
.row:hover, .row.is-selected { background: var(--soft); }
.row.is-selected { border-left-color: var(--accent); }
.row > span {
min-width: 0;
max-width: 100%;
}
.row > span:last-child {
display: flex;
min-width: 0;
max-width: 100%;
flex-direction: column;
align-items: flex-start;
}
.type { color: var(--muted); font-size: 0.78rem; line-height: 1.25; overflow-wrap: anywhere; }
.title { color: var(--ink); font-weight: 650; line-height: 1.3; overflow-wrap: anywhere; word-break: break-word; }
.row-support {
display: block;
margin-top: 0.22rem;
color: var(--muted);
font-size: 0.78rem;
line-height: 1.35;
overflow-wrap: anywhere;
word-break: break-word;
}
.action {
display: inline-flex;
min-width: 0;
max-width: 9rem;
border: 1px solid var(--border);
border-radius: 999px;
padding: 0.2rem 0.48rem;
background: var(--surface);
color: var(--warning);
font-size: 0.72rem;
font-weight: 600;
line-height: 1.2;
text-align: center;
overflow-wrap: anywhere;
white-space: normal;
}
.row-status {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
align-items: center;
margin-top: 0.28rem;
color: var(--muted);
font-size: 0.76rem;
line-height: 1.25;
}
.row-status::before {
content: "";
width: 0.42rem;
height: 0.42rem;
border-radius: 999px;
background: var(--warning);
}
.row-status.is-ready::before, .row-status.is-accepted::before, .row-status.is-reviewed::before { background: var(--ok); }
.row-status.is-needs_evidence::before, .row-status.is-rejected::before, .row-status.is-blocked::before { background: var(--danger); }
.row-status.is-edited::before { background: var(--info); }
.action--accept, .action--accepted { background: var(--surface); color: var(--ok); }
.action--mark_unclear, .action--request_more_documents, .action--reject, .action--rejected, .action--needs_evidence {
background: var(--surface);
color: var(--danger);
}
.action--edit, .action--edited { background: var(--surface); color: var(--info); }
.details { padding: 1rem; }
.review-heading {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.8rem;
align-items: start;
padding-bottom: 0.85rem;
border-bottom: 1px solid var(--border);
}
.review-heading > div { min-width: 0; }
.review-heading h3 { margin: 0 0 0.25rem; color: var(--ink); font-size: 1.05rem; line-height: 1.25; overflow-wrap: anywhere; word-break: break-word; }
.review-heading p { margin: 0; color: var(--muted); font-size: 0.86rem; line-height: 1.4; overflow-wrap: anywhere; word-break: break-word; }
.details dl {
display: grid;
grid-template-columns: minmax(7.5rem, 0.32fr) minmax(0, 1fr);
gap: 0.42rem 0.75rem;
margin: 0.85rem 0;
}
.details dt { color: var(--muted); font-size: 0.8rem; line-height: 1.3; }
.details dd { margin: 0; color: var(--text); overflow-wrap: anywhere; }
.decision-box {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.75rem;
margin: 0.85rem 0;
background: var(--surface);
}
.decision-grid { display: flex; flex-wrap: wrap; gap: 0.42rem; margin-bottom: 0.65rem; }
.decision-impact {
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 0.72rem;
background: var(--soft);
overflow: hidden;
}
.decision-impact__title {
padding: 0.56rem 0.68rem;
border-bottom: 1px solid var(--border);
color: var(--ink);
font-size: 0.82rem;
font-weight: 650;
line-height: 1.25;
}
.decision-impact__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
gap: 0;
}
.decision-impact__item {
display: grid;
gap: 0.16rem;
min-width: 0;
padding: 0.58rem 0.68rem;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
}
.decision-impact__item span {
color: var(--muted);
font-size: 0.72rem;
font-weight: 620;
line-height: 1.2;
}
.decision-impact__item strong {
color: var(--text);
font-size: 0.82rem;
font-weight: 560;
line-height: 1.3;
overflow-wrap: anywhere;
}
.field { display: grid; gap: 0.25rem; margin-top: 0.55rem; }
.field-label { color: var(--muted); font-size: 0.78rem; line-height: 1.25; }
textarea {
width: 100%;
min-height: 5rem;
resize: vertical;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.6rem 0.7rem;
background: var(--surface);
color: var(--ink);
outline: none;
overflow-wrap: anywhere;
}
.review-section { margin-top: 1rem; }
.review-section h4 { margin: 0 0 0.48rem; color: var(--ink); font-size: 0.86rem; font-weight: 650; }
.workflow-detail {
min-width: 0;
margin-top: 1rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
overflow: hidden;
}
.workflow-detail__header {
display: grid;
gap: 0.18rem;
padding: 0.78rem 0.85rem;
border-bottom: 1px solid var(--border);
background: var(--surface);
}
.workflow-detail__header strong {
color: var(--ink);
font-size: 0.92rem;
line-height: 1.25;
overflow-wrap: anywhere;
word-break: break-word;
}
.workflow-detail__header span {
color: var(--muted);
font-size: 0.8rem;
line-height: 1.35;
overflow-wrap: anywhere;
}
.workflow-detail__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
gap: 0;
}
.workflow-card {
min-width: 0;
padding: 0.86rem;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
background: var(--surface);
}
.workflow-card h5 {
margin: 0 0 0.55rem;
color: var(--ink);
font-size: 0.82rem;
font-weight: 650;
line-height: 1.25;
overflow-wrap: anywhere;
word-break: break-word;
}
.workflow-card--prose .kv div:nth-child(even),
.workflow-card--draft .kv div:nth-child(even) {
color: var(--ink);
font-size: 0.98rem;
line-height: 1.45;
}
.workflow-card--ledger .kv {
grid-template-columns: minmax(5.5rem, 0.55fr) minmax(0, 1fr);
gap: 0.32rem 0.65rem;
}
.workflow-card--finding {
background: var(--surface);
}
.workflow-detail--claim-desk .workflow-card--prose,
.workflow-detail--report-editor .workflow-card--draft,
.workflow-detail--prompt-lab .workflow-card--draft {
grid-column: span 2;
}
.workflow-detail--match-ledger .workflow-card--ledger,
.workflow-detail--reconciliation-desk .workflow-card--ledger,
.workflow-detail--sample-sheet .workflow-card--ledger {
background: var(--surface);
}
.workflow-detail--intake-board .workflow-card--checklist,
.workflow-detail--prompt-lab .workflow-card--checklist {
background: var(--surface);
}
.evidence-columns {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: 0.65rem;
}
.evidence-pane, .evidence-card {
min-width: 0;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.68rem;
background: var(--surface);
}
.evidence-pane h5, .evidence-card strong {
display: block;
margin: 0 0 0.44rem;
color: var(--ink);
font-size: 0.8rem;
font-weight: 650;
line-height: 1.25;
}
.kv {
display: grid;
grid-template-columns: 1fr;
gap: 0.2rem;
}
.kv div:nth-child(odd) { color: var(--muted); font-size: 0.77rem; line-height: 1.25; }
.kv div:nth-child(even) { min-width: 0; color: var(--text); overflow-wrap: anywhere; }
.evidence-list { display: grid; gap: 0.5rem; }
.artifact-strip {
margin-top: 0.9rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
overflow: hidden;
}
.artifact-header {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: center;
min-height: 3rem;
border-bottom: 1px solid var(--border);
padding: 0.72rem 0.9rem;
background: var(--surface);
}
.artifact-header h2 { margin: 0; color: var(--ink); font-size: 0.95rem; }
.artifact-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.artifact-notes {
display: grid;
gap: 0.45rem;
border-bottom: 1px solid var(--border);
padding: 0.68rem 0.9rem;
background: var(--soft);
}
.artifact-note {
display: grid;
grid-template-columns: minmax(5.8rem, 0.2fr) minmax(0, 1fr);
gap: 0.55rem;
align-items: start;
color: var(--text);
font-size: 0.8rem;
line-height: 1.35;
}
.artifact-note strong {
color: var(--ink);
font-size: 0.78rem;
font-weight: 650;
line-height: 1.35;
}
.artifact-note span { min-width: 0; color: var(--muted); overflow-wrap: anywhere; }
.artifact-item {
min-width: 0;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
padding: 0.78rem 0.9rem;
}
.artifact-item strong { display: block; color: var(--ink); font-size: 0.88rem; overflow-wrap: anywhere; }
.artifact-item span { display: block; margin-top: 0.2rem; color: var(--muted); font-size: 0.78rem; overflow-wrap: anywhere; }
.artifact-tags {
display: flex;
flex-wrap: wrap;
gap: 0.28rem;
margin-top: 0.55rem;
}
.artifact-tag {
display: inline-flex;
align-items: center;
gap: 0.3rem;
min-height: 1.45rem;
border: 1px solid var(--border);
border-radius: 999px;
padding: 0.16rem 0.45rem;
background: var(--surface);
color: var(--muted);
font-size: 0.72rem;
line-height: 1.2;
overflow-wrap: anywhere;
}
.artifact-tag strong {
display: inline;
color: var(--ink);
font-size: inherit;
font-weight: 650;
}
.artifact-qa {
margin-top: 0.62rem;
border: 1px solid var(--border);
border-radius: 0.42rem;
background: var(--soft);
}
.artifact-qa summary {
cursor: pointer;
padding: 0.48rem 0.58rem;
color: var(--ink);
font-size: 0.76rem;
font-weight: 650;
line-height: 1.25;
}
.artifact-qa-grid {
display: grid;
gap: 0.5rem;
border-top: 1px solid var(--border);
padding: 0.58rem;
}
.artifact-qa-section {
display: grid;
gap: 0.28rem;
min-width: 0;
}
.artifact-qa-section strong {
color: var(--ink);
font-size: 0.74rem;
font-weight: 650;
line-height: 1.25;
}
.artifact-qa-section ul {
display: grid;
gap: 0.18rem;
margin: 0;
padding-left: 1rem;
color: var(--muted);
font-size: 0.74rem;
line-height: 1.32;
overflow-wrap: anywhere;
}
.empty, .empty-state { color: var(--muted); }
.empty { padding: 1.25rem; }
.empty-state { display: grid; gap: 0.85rem; padding: 1.25rem; min-height: 15rem; align-content: center; }
.empty-state strong { color: var(--ink); font-size: 1rem; line-height: 1.25; }
.empty-state p { max-width: 58ch; margin: 0; line-height: 1.45; }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
}
@media (max-width: 980px) {
.content, .toolbar, .run-context, .data-posture, .execution-provenance, .safeguards, .review-actions, .state-strip { grid-template-columns: 1fr; }
.top-actions, .context-list, .tabs, .action-buttons, .state-filters { justify-content: flex-start; }
.state-final { border-left: 0; border-top: 1px solid var(--border); padding: 0.68rem 0 0; }
.progress-meter { grid-template-columns: 1fr; }
}
@media (max-width: 760px) {
.shell { display: flex; flex-direction: column; }
.app { padding: 1rem; }
.topbar { flex-direction: column; align-items: stretch; }
.topbar { order: 1; }
.summary { order: 2; }
.run-context { order: 3; }
.review-actions { order: 4; }
.state-strip { order: 5; }
.toolbar { order: 6; }
.content { order: 7; }
.artifact-strip { order: 8; }
.data-posture { order: 9; }
.execution-provenance { order: 10; }
.safeguards { order: 11; }
.summary, .data-posture__grid, .review-store, .artifact-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.safeguards__grid { grid-template-columns: 1fr; }
.metric { padding-right: 0.65rem; }
.action-buttons > *, .tabs > *, .state-filters > * { flex: 1 1 100%; justify-content: center; }
.row { grid-template-columns: 1fr; }
.row .action { max-width: 100%; }
.review-heading { grid-template-columns: 1fr; }
.review-heading .status-token { justify-self: start; }
.workflow-detail__grid { grid-template-columns: 1fr; }
.workflow-detail--claim-desk .workflow-card--prose,
.workflow-detail--report-editor .workflow-card--draft,
.workflow-detail--prompt-lab .workflow-card--draft { grid-column: auto; }
.decision-grid { display: grid; grid-template-columns: 1fr; }
.decision-choice { width: 100%; }
.decision-impact__grid { grid-template-columns: 1fr; }
.details dl, .execution-kv, .recovery-panel__line, .workflow-card--ledger .kv, .kv { grid-template-columns: 1fr; }
}
@media (max-width: 430px) {
.app { padding: 1rem 0.9rem; overflow-x: hidden; }
.summary, .data-posture__grid, .safeguards__grid, .review-store, .artifact-grid { grid-template-columns: 1fr; }
.metric { min-height: 3.2rem; border-right: 0; border-bottom: 1px solid var(--border); padding: 0.62rem 0; }
.metric:last-child { border-bottom: 0; }
.action-buttons > *, .tabs > *, .state-filters > * { flex-basis: 100%; justify-content: center; }
.progress-rail { min-width: 0; }
.execution-kv, .recovery-panel__line, .workflow-card--ledger .kv, .kv { grid-template-columns: 1fr; }
}
.shell > * { order: 0 !important; min-width: 0; }
.technical-details { border-top: 1px solid var(--border); padding: 12px 0; }
.technical-details > summary { cursor: pointer; padding: 8px; }
@media (max-width: 600px) {
.summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.summary .metric { padding: 10px; min-height: 0; }
.tabs, .state-filters { flex-wrap: nowrap; overflow-x: auto; max-width: 100%; padding-bottom: 4px; }
.tabs > *, .state-filters > * { flex: 0 0 auto; white-space: nowrap; }
}
</style>
</head>
<body>
<main class="app">
<div class="shell">
<section class="topbar">
<div>
<h1 id="app-title">Build Report Review</h1>
<p class="subtitle" id="subtitle">Loading payload...</p>
</div>
<div class="top-actions">
<span class="pill" id="status-pill">MCP widget</span>
<span class="pill" id="decision-pill">0 reviewed</span>
</div>
</section>
<section class="summary" id="summary"></section>
<section class="state-strip" id="state-strip"></section>
<section class="toolbar">
<input id="search" class="search" type="search" placeholder="Search section, table, source, blocker, artifact">
<div class="tabs" id="tabs"></div>
</section>
<section class="content">
<div class="panel">
<div class="panel-header"><h2 id="queue-title">Review queue</h2><span id="count"></span></div>
<div class="rows" id="rows"></div>
</div>
<div class="panel">
<div class="panel-header"><h2 id="detail-title">Evidence and decision</h2></div>
<div class="details" id="details"></div>
</div>
</section>
<section class="artifact-strip" id="artifact-strip"></section>
<section class="review-actions">
<div class="progress-meter">
<strong id="decision-progress">0 / 0 decided</strong>
<div class="progress-rail" aria-hidden="true"><span id="decision-progress-fill"></span></div>
<span class="status-message" id="save-status"></span>
</div>
<div class="action-buttons">
<button class="button" id="use-recommended" type="button">Use recommended</button>
<button class="button primary" id="save-decisions" type="button">Save decisions</button>
<button class="button" id="apply-decisions" type="button">Apply decisions</button>
<button class="button" id="copy-decisions" type="button">Copy JSON</button>
<button class="button" id="download-decisions" type="button">Download JSON</button>
</div>
<div class="review-store" id="review-store"></div>
<div class="recovery-panel" id="recovery-panel"></div>
</section>
<details class="technical-details"><summary id="technical-title">Technical details</summary>
<section class="run-context" id="run-context"></section>
<section class="data-posture" id="data-posture"></section>
<section class="execution-provenance" id="execution-provenance"></section>
<section class="safeguards" id="review-safeguards"></section>
</details>
</div>
</main>
<script>
const CONFIG = {"schemaVersion":"1.0","plugin":"report-builder","title":"Build Report Review","reviewTitle":"Report Section Review","queueTitle":"Report Sections","detailTitle":"Section Editor","detailMode":"report-editor","detailHelp":"Review each narrative section against its source table and capture revisions.","detailGroups":[{"title":"Draft Section","variant":"draft","fields":["section","codex_comment","draft_text","prompt","status"],"empty":"No draft text."},{"title":"Source Table","variant":"source","fields":["source_table","source_path","sheet_name","preview_rows","metric","period","amount"],"empty":"No source table fields."},{"title":"Revision Note","variant":"finding","fields":["review_notes","reason","blocker","proposed_fix"],"empty":"No revision note."}],"panels":["Report section","Source table","Review note"],"localized":{"es":{"title":"Revisi\u00f3n de informe","reviewTitle":"Revisi\u00f3n de secciones del informe","queueTitle":"Secciones del informe","detailTitle":"Editor de secci\u00f3n","detailHelp":"Revise cada secci\u00f3n narrativa con su tabla fuente y registre las modificaciones.","search":"Buscar secci\u00f3n, tabla, fuente, bloqueo, salida"},"it":{"title":"Revisione report","reviewTitle":"Revisione sezioni report","queueTitle":"Sezioni report","detailTitle":"Editor sezione","detailHelp":"Rivedi ogni sezione narrativa rispetto alla tabella fonte e cattura revisioni.","search":"Cerca sezione, tabella, fonte, blocco, output"},"fr":{"title":"Revue rapport","reviewTitle":"Revue des sections du rapport","queueTitle":"Sections rapport","detailTitle":"Editeur section","detailHelp":"Revoyez chaque section narrative avec sa table source et saisissez les revisions.","search":"Chercher section, table, source, blocage, sortie"},"de":{"title":"Report Review","reviewTitle":"Berichtsabschnitte pruefen","queueTitle":"Berichtsabschnitte","detailTitle":"Abschnittseditor","detailHelp":"Jeden narrativen Abschnitt gegen die Quelltabelle pruefen und Ueberarbeitungen erfassen.","search":"Abschnitt, Tabelle, Quelle, Blocker, Ausgabe suchen"}},"saveTool":"save_report_builder_decisions","applyTool":"apply_report_builder_decisions","widgetType":"report_builder_review","demo":{"review_type":"report_builder_review","items":[{"id":"section-1","item_type":"report_section","title":"Liquidity bridge","source_path":"cash_flow.xlsx","output_path":"report.docx","allowed_actions":["accept","reject","edit","mark_unclear","skip"],"recommended_action":"edit","data":{"section":"Liquidity bridge","source_table":"cash_flow.xlsx","status":"drafted","draft_text":"Operating cash improved, but the bridge needs a separate one-off tax-payment explanation.","metric":"Net cash movement","period":"Q1 2026","amount":"-42000","review_notes":"Needs clearer explanation of one-off tax payment.","target_artifact":"report_draft.md","target_id_field":"section","target_record_id":"Liquidity bridge","target_field":"draft_text","edit_hint":"Editing this section updates the Liquidity bridge draft text before DOCX regeneration."},"evidence":[{"kind":"section_vs_source","source_table":"cash_flow.xlsx"}]},{"id":"section-2","item_type":"review_issue","title":"Revenue trend - missing source table","source_path":"sales_tables/","output_path":"report_draft.md","allowed_actions":["accept","reject","edit","mark_unclear","request_more_documents","skip"],"recommended_action":"request_more_documents","data":{"section":"Revenue trend","status":"needs_evidence","source_table":"not mapped","blocker":"Missing revenue table for the requested period.","requested_document":"Revenue table or trial balance schedule for Q1 2026","reason":"The report section cannot be drafted from the available local tables.","target_artifact":"report_draft.md","target_id_field":"section","target_record_id":"Revenue trend","target_field":"draft_text","edit_hint":"Editing this row records the reviewer draft for the Revenue trend section."},"evidence":[{"kind":"missing_source_table","status":"needs_evidence","requested_document":"Revenue table or trial balance schedule for Q1 2026"}]}]},"injectLocalServer":false};
const ACTION_LABELS = {
accept: "Accept",
reject: "Reject",
edit: "Edit",
mark_unclear: "Mark unclear",
request_more_documents: "Request documents",
skip: "Skip",
};
const ACTION_STATUSES = {
accept: "accepted",
reject: "rejected",
edit: "edited",
mark_unclear: "needs_evidence",
request_more_documents: "needs_evidence",
skip: "skipped",
};
const UI_TEXT = {"en":{"actions":{"accept":"Accept","reject":"Reject","edit":"Edit","mark_unclear":"Mark unclear","request_more_documents":"Request documents","skip":"Skip"},"statuses":{"accepted":"Accepted","rejected":"Rejected","edited":"Edited","needs_evidence":"Needs evidence","skipped":"Skipped","pending":"Pending","pending_review":"Pending review","partial_review":"Partial review","reviewed":"Reviewed","ready":"Ready","ready_for_review":"Ready for review","empty":"Empty","written":"Written","written_pending_review":"Written, pending review","final_ready":"Final ready","blocked":"Blocked","partial_review_applied":"Partially applied"},"metrics":{"items":"Items","issues":"Issues","evidence":"Evidence","artifacts":"Artifacts","reviewed":"Reviewed"},"buttons":{"useRecommended":"Use recommended","saveDecisions":"Save decisions","applyDecisions":"Apply decisions","copyJson":"Copy JSON","downloadJson":"Download JSON","previewSample":"Preview sample review","copyEmpty":"Copy empty decision JSON"},"fields":{"type":"Type","recommended":"Recommended","decision":"Decision","source":"Source","output":"Output","reviewerNote":"Reviewer note","editValue":"Edit value","requestedDocuments":"Requested documents"},"text":{"loadingPayload":"Loading payload...","mcpWidget":"MCP widget","reviewedSuffix":"decisions selected","decidedProgress":"{decided} / {total} decided","runLabel":"Run","noSourcePaths":"No source paths supplied","writesDecisions":"Writes ui_decisions.json","jsonFallback":"JSON fallback","decisionImpact":"Decision impact","currentAction":"Action","writeMode":"Write mode","writeTarget":"Write target","documentHint":"Document hint","reviewStore":"Review state","currentDecisions":"Current","savedDecisions":"Saved","appliedDecisions":"Applied","currentDecisionCount":"{count} current","savedDecisionCount":"{count} saved","appliedDecisionCount":"{count} applied","unsavedDecisionCount":"{count} unsaved","inSync":"In sync","recoveredDraftCount":"{count} recovered draft","fallbackActive":"JSON fallback active","fallbackBody":"Copy or download ui_decisions.json. Use the MCP or local-server review surface to persist and apply decisions.","applyRequiresMcp":"Apply requires the MCP or local-server review surface.","lastError":"Last error","recoveryAction":"Recovery","retryWithMcp":"Retry from the MCP or local-server review surface, or copy/download decision JSON for handoff.","toolLabel":"Tool","saveFailedTitle":"Save did not persist","applyFailedTitle":"Apply did not persist","mcpWriteMode":"MCP save/apply","copyOnlyMode":"Copy JSON only","noDecisionYet":"No decision selected","revisionOnly":"Revision file only until workflow apply updates a native artifact","notTargeted":"No direct artifact target","targetRecord":"record","targetField":"field","targetRecords":"records","all":"All","allStates":"All states","artifactState":"Artifact state","reviewQueue":"Review queue","evidenceDecision":"Evidence and decision","noPayloadTitle":"No review payload loaded","noPayloadBody":"This widget is waiting for an MCP render call with review_payload.json. Opening the HTML file directly does not include run data.","noFilterMatch":"No item matches the current filters.","noFields":"No fields.","detail":"Detail","reviewEvidence":"Review the evidence and capture the decision.","evidenceTrail":"Evidence trail","waitingTitle":"Waiting for review data","waitingBody":"Render this widget from the MCP tool to load review items, evidence, and outputs.","selectItem":"Select a review item.","readyDecision":"Ready for reviewer decision.","finalOutputs":"Final outputs","fileSingular":"file","filePlural":"files","output":"Output","outputsPending":"Outputs appear after the run writes final artifacts.","artifactRows":"Rows","artifactMinRows":"Min rows","artifactSheets":"Sheets","artifactHeaders":"Headers","artifactCells":"Cells","artifactText":"Text","artifactColumns":"Columns","artifactRecords":"Records","artifactQaDetails":"Verification details","artifactQaChecks":"QA checks","artifactRequiredText":"Required text","artifactRequiredColumns":"Required columns","artifactRequiredSheets":"Required sheets","artifactRequiredHeaders":"Required headers","artifactRequiredCells":"Required cells","artifactBytes":"bytes","artifactMore":"+{count} more","dataPosture":"Helper execution routes","dataPostureScope":"Optional routes used by helper execution. This does not describe or count the context sent by Codex to the model.","localFiles":"Files read by helpers","connectors":"Optional connectors","uploads":"Optional upload routes","remoteSql":"Helper remote SQL","hostedNotebook":"Helper hosted notebook","executionProvenance":"Execution provenance","executionPending":"Execution trace appears after run_intake.json records the local run.","traceStepSingular":"step","traceStepPlural":"steps","executionLocation":"Location","executionKind":"Kind","executionStatus":"Status","executionCommand":"Command","executionInputs":"Inputs","executionOutputs":"Outputs","localExecution":"Local","remoteExecution":"Remote","reviewSafeguards":"Review safeguards","safeguardsReady":"Ready","safeguardsCheck":"Check","safeguardsWarning":"Review","safeguardLocalExecution":"Helper-script execution","safeguardExternalRoute":"Optional routes beyond Codex","safeguardBoundedPayload":"Bounded payload","safeguardDecisionPersistence":"Decision persistence","safeguardFinalArtifacts":"Final artifacts","externalRouteUsed":"Route used and recorded","externalRouteNotUsed":"No optional route used","payloadItemsCount":"{count} items","finalArtifactsDeclared":"{count} outputs","finalArtifactsMissing":"No outputs declared","executionTraceMissing":"Trace missing","localExecutionDeclared":"Helper ran in local workspace","remoteExecutionDeclared":"Helper used an external path","persistenceAvailable":"Save/apply available","persistenceFallback":"Copy JSON only","notDeclared":"Not declared","noneValue":"None","blockers":"Blockers","caveats":"Caveats","nextActions":"Next actions","yes":"Yes","no":"No","editRequired":"Edit value required for {item}.","saving":"Saving...","savingApplying":"Saving and applying...","decisionsSaved":"Decisions saved.","decisionsApplied":"Decisions applied.","toolCopied":"Tool bridge unavailable. JSON copied.","toolCopiedApply":"Tool bridge unavailable. JSON copied; apply requires MCP.","jsonDownloaded":"JSON downloaded.","jsonCopied":"JSON copied.","emptyJsonCopied":"Empty decision JSON copied.","sampleLoaded":"Sample review loaded.","saveFailed":"Decision save failed.","applyFailed":"Decision apply failed."}},"es":{"actions":{"accept":"Aceptar","reject":"Rechazar","edit":"Editar","mark_unclear":"Marcar como incierto","request_more_documents":"Solicitar documentos","skip":"Omitir"},"statuses":{"accepted":"Aceptado","rejected":"Rechazado","edited":"Editado","needs_evidence":"Requiere evidencia","skipped":"Omitido","pending":"Pendiente","pending_review":"Pendiente de revisi\u00f3n","partial_review":"Revisi\u00f3n parcial","reviewed":"Revisado","ready":"Listo","ready_for_review":"Listo para revisi\u00f3n","empty":"Vac\u00edo","written":"Generado","written_pending_review":"Generado, pendiente de revisi\u00f3n","final_ready":"Versi\u00f3n final lista","blocked":"Bloqueado","partial_review_applied":"Aplicado parcialmente"},"metrics":{"items":"Elementos","issues":"Incidencias","evidence":"Evidencias","artifacts":"Artefactos","reviewed":"Revisados"},"buttons":{"useRecommended":"Usar recomendaciones","saveDecisions":"Guardar decisiones","applyDecisions":"Aplicar decisiones","copyJson":"Copiar JSON","downloadJson":"Descargar JSON","previewSample":"Ver revisi\u00f3n de ejemplo","copyEmpty":"Copiar JSON de decisiones vac\u00edo"},"fields":{"type":"Tipo","recommended":"Recomendado","decision":"Decisi\u00f3n","source":"Fuente","output":"Salida","reviewerNote":"Nota del revisor","editValue":"Valor editado","requestedDocuments":"Documentos solicitados"},"text":{"loadingPayload":"Cargando datos...","mcpWidget":"Widget MCP","reviewedSuffix":"decisiones seleccionadas","decidedProgress":"{decided} / {total} decididos","runLabel":"Ejecuci\u00f3n","noSourcePaths":"No se han indicado rutas de origen","writesDecisions":"Escribe ui_decisions.json","jsonFallback":"Modo alternativo JSON","decisionImpact":"Impacto de la decisi\u00f3n","currentAction":"Acci\u00f3n","writeMode":"Modo de escritura","writeTarget":"Destino de escritura","documentHint":"Documento sugerido","reviewStore":"Estado de la revisi\u00f3n","currentDecisions":"Actuales","savedDecisions":"Guardadas","appliedDecisions":"Aplicadas","currentDecisionCount":"{count} actuales","savedDecisionCount":"{count} guardadas","appliedDecisionCount":"{count} aplicadas","unsavedDecisionCount":"{count} sin guardar","inSync":"Sincronizado","recoveredDraftCount":"{count} borrador recuperado","fallbackActive":"Modo alternativo JSON activo","fallbackBody":"Copie o descargue ui_decisions.json. Use la superficie MCP o el servidor local para guardar y aplicar las decisiones.","applyRequiresMcp":"La aplicaci\u00f3n requiere la superficie MCP o el servidor local.","lastError":"\u00daltimo error","recoveryAction":"Recuperaci\u00f3n","retryWithMcp":"Vuelva a intentarlo desde la superficie MCP o el servidor local, o copie/descargue el JSON de decisiones para entregarlo.","toolLabel":"Herramienta","saveFailedTitle":"No se ha podido guardar","applyFailedTitle":"No se ha podido aplicar","mcpWriteMode":"Guardar/aplicar con MCP","copyOnlyMode":"Solo copiar JSON","noDecisionYet":"A\u00fan no se ha seleccionado ninguna decisi\u00f3n","revisionOnly":"Solo archivo de revisi\u00f3n hasta que el flujo de aplicaci\u00f3n actualice un artefacto nativo","notTargeted":"Sin destino de artefacto directo","targetRecord":"registro","targetField":"campo","targetRecords":"registros","all":"Todos","allStates":"Todos los estados","artifactState":"Estado del artefacto","reviewQueue":"Cola de revisi\u00f3n","evidenceDecision":"Evidencia y decisi\u00f3n","noPayloadTitle":"No hay datos de revisi\u00f3n cargados","noPayloadBody":"Este widget espera una llamada de renderizado MCP con review_payload.json. Abrir directamente el archivo HTML no incluye los datos de la ejecuci\u00f3n.","noFilterMatch":"Ning\u00fan elemento coincide con los filtros actuales.","noFields":"No hay campos.","detail":"Detalle","reviewEvidence":"Revise la evidencia y registre la decisi\u00f3n.","evidenceTrail":"Trazabilidad de la evidencia","waitingTitle":"Esperando datos de revisi\u00f3n","waitingBody":"Renderice este widget desde la herramienta MCP para cargar los elementos, las evidencias y las salidas.","selectItem":"Seleccione un elemento para revisar.","readyDecision":"Listo para la decisi\u00f3n del revisor.","finalOutputs":"Salidas finales","fileSingular":"archivo","filePlural":"archivos","output":"Salida","outputsPending":"Las salidas aparecer\u00e1n cuando la ejecuci\u00f3n genere los artefactos finales.","artifactRows":"Filas","artifactMinRows":"Filas m\u00ednimas","artifactSheets":"Hojas","artifactHeaders":"Encabezados","artifactCells":"Celdas","artifactText":"Texto","artifactColumns":"Columnas","artifactRecords":"Registros","artifactQaDetails":"Detalles de verificaci\u00f3n","artifactQaChecks":"Comprobaciones de calidad","artifactRequiredText":"Texto obligatorio","artifactRequiredColumns":"Columnas obligatorias","artifactRequiredSheets":"Hojas obligatorias","artifactRequiredHeaders":"Encabezados obligatorios","artifactRequiredCells":"Celdas obligatorias","artifactBytes":"bytes","artifactMore":"+{count} m\u00e1s","dataPosture":"Rutas de ejecuci\u00f3n auxiliares","dataPostureScope":"Rutas opcionales usadas por la ejecuci\u00f3n auxiliar. Esto no describe ni contabiliza el contexto que Codex env\u00eda al modelo.","localFiles":"Archivos le\u00eddos por los procesos auxiliares","connectors":"Conectores opcionales","uploads":"Rutas de carga opcionales","remoteSql":"SQL remoto auxiliar","hostedNotebook":"Cuaderno alojado auxiliar","executionProvenance":"Procedencia de la ejecuci\u00f3n","executionPending":"La traza de ejecuci\u00f3n aparecer\u00e1 cuando run_intake.json registre la ejecuci\u00f3n local.","traceStepSingular":"paso","traceStepPlural":"pasos","executionLocation":"Ubicaci\u00f3n","executionKind":"Tipo","executionStatus":"Estado","executionCommand":"Comando","executionInputs":"Entradas","executionOutputs":"Salidas","localExecution":"Local","remoteExecution":"Remota","reviewSafeguards":"Salvaguardas de revisi\u00f3n","safeguardsReady":"Listo","safeguardsCheck":"Comprobar","safeguardsWarning":"Revisar","safeguardLocalExecution":"Ejecuci\u00f3n de scripts auxiliares","safeguardExternalRoute":"Rutas opcionales fuera de Codex","safeguardBoundedPayload":"Datos acotados","safeguardDecisionPersistence":"Persistencia de decisiones","safeguardFinalArtifacts":"Artefactos finales","externalRouteUsed":"Ruta utilizada y registrada","externalRouteNotUsed":"No se ha usado ninguna ruta opcional","payloadItemsCount":"{count} elementos","finalArtifactsDeclared":"{count} salidas","finalArtifactsMissing":"No se han declarado salidas","executionTraceMissing":"Falta la traza","localExecutionDeclared":"El proceso auxiliar se ejecut\u00f3 en el espacio de trabajo local","remoteExecutionDeclared":"El proceso auxiliar utiliz\u00f3 una ruta externa","persistenceAvailable":"Guardado/aplicaci\u00f3n disponibles","persistenceFallback":"Solo copiar JSON","notDeclared":"No declarado","noneValue":"Ninguno","blockers":"Bloqueos","caveats":"Advertencias","nextActions":"Pr\u00f3ximas acciones","yes":"S\u00ed","no":"No","editRequired":"Se requiere un valor editado para {item}.","saving":"Guardando...","savingApplying":"Guardando y aplicando...","decisionsSaved":"Decisiones guardadas.","decisionsApplied":"Decisiones aplicadas.","toolCopied":"El puente de herramientas no est\u00e1 disponible. JSON copiado.","toolCopiedApply":"El puente de herramientas no est\u00e1 disponible. JSON copiado; la aplicaci\u00f3n requiere MCP.","jsonDownloaded":"JSON descargado.","jsonCopied":"JSON copiado.","emptyJsonCopied":"JSON de decisiones vac\u00edo copiado.","sampleLoaded":"Revisi\u00f3n de ejemplo cargada.","saveFailed":"No se han podido guardar las decisiones.","applyFailed":"No se han podido aplicar las decisiones."}},"it":{"actions":{"accept":"Accetta","reject":"Rifiuta","edit":"Modifica","mark_unclear":"Segna non chiaro","request_more_documents":"Richiedi documenti","skip":"Salta"},"statuses":{"accepted":"Accettato","rejected":"Rifiutato","edited":"Modificato","needs_evidence":"Serve evidenza","skipped":"Saltato","pending":"In attesa","pending_review":"Revisione in attesa","partial_review":"Revisione parziale","reviewed":"Rivisto","ready":"Pronto","ready_for_review":"Pronto per revisione","empty":"Vuoto","written":"Scritto","written_pending_review":"Scritto, revisione in attesa","final_ready":"Finale pronto","blocked":"Bloccato","partial_review_applied":"Applicato parzialmente"},"metrics":{"items":"Elementi","issues":"Rilievi","evidence":"Evidenze","artifacts":"Output","reviewed":"Rivisti"},"buttons":{"useRecommended":"Usa raccomandate","saveDecisions":"Salva decisioni","applyDecisions":"Applica decisioni","copyJson":"Copia JSON","downloadJson":"Scarica JSON","previewSample":"Apri esempio","copyEmpty":"Copia JSON vuoto"},"fields":{"type":"Tipo","recommended":"Raccomandato","decision":"Decisione","source":"Fonte","output":"Output","reviewerNote":"Nota revisore","editValue":"Valore modificato","requestedDocuments":"Documenti richiesti"},"text":{"loadingPayload":"Caricamento payload...","mcpWidget":"Widget MCP","reviewedSuffix":"decisioni selezionate","decidedProgress":"{decided} / {total} decisi","runLabel":"Run","noSourcePaths":"Nessun percorso sorgente fornito","writesDecisions":"Scrive ui_decisions.json","jsonFallback":"Fallback JSON","decisionImpact":"Impatto decisione","currentAction":"Azione","writeMode":"Scrittura","writeTarget":"Target scrittura","documentHint":"Documento suggerito","reviewStore":"Stato revisione","currentDecisions":"Correnti","savedDecisions":"Salvate","appliedDecisions":"Applicate","currentDecisionCount":"{count} correnti","savedDecisionCount":"{count} salvate","appliedDecisionCount":"{count} applicate","unsavedDecisionCount":"{count} non salvate","inSync":"Allineato","recoveredDraftCount":"{count} bozza recuperata","fallbackActive":"Fallback JSON attivo","fallbackBody":"Copia o scarica ui_decisions.json. Usa la superficie MCP o server locale per salvare e applicare decisioni.","applyRequiresMcp":"Applicare richiede la superficie MCP o server locale.","lastError":"Ultimo errore","recoveryAction":"Recupero","retryWithMcp":"Riprova dalla superficie MCP o server locale, oppure copia/scarica il JSON decisioni per handoff.","toolLabel":"Strumento","saveFailedTitle":"Salvataggio non persistito","applyFailedTitle":"Applicazione non persistita","mcpWriteMode":"Salva/applica MCP","copyOnlyMode":"Solo copia JSON","noDecisionYet":"Nessuna decisione","revisionOnly":"Solo file revisione finche il workflow aggiorna l'artefatto nativo","notTargeted":"Nessun target diretto","targetRecord":"record","targetField":"campo","targetRecords":"record","all":"Tutti","allStates":"Tutti gli stati","artifactState":"Stato output","reviewQueue":"Coda revisione","evidenceDecision":"Evidenza e decisione","noPayloadTitle":"Nessun payload di revisione caricato","noPayloadBody":"Il widget attende una chiamata MCP con review_payload.json. Aprire il file HTML direttamente non include i dati del run.","noFilterMatch":"Nessun elemento corrisponde ai filtri correnti.","noFields":"Nessun campo.","detail":"Dettaglio","reviewEvidence":"Rivedi l'evidenza e registra la decisione.","evidenceTrail":"Traccia evidenze","waitingTitle":"In attesa dei dati di revisione","waitingBody":"Apri il widget dallo strumento MCP per caricare elementi, evidenze e output.","selectItem":"Seleziona un elemento.","readyDecision":"Pronto per la decisione del revisore.","finalOutputs":"Output finali","fileSingular":"file","filePlural":"file","output":"Output","outputsPending":"Gli output appaiono dopo la scrittura degli artefatti finali.","artifactRows":"Righe","artifactMinRows":"Min righe","artifactSheets":"Fogli","artifactHeaders":"Intestazioni","artifactCells":"Celle","artifactText":"Testo","artifactColumns":"Colonne","artifactRecords":"Record","artifactQaDetails":"Dettagli verifica","artifactQaChecks":"Controlli QA","artifactRequiredText":"Testo richiesto","artifactRequiredColumns":"Colonne richieste","artifactRequiredSheets":"Fogli richiesti","artifactRequiredHeaders":"Intestazioni richieste","artifactRequiredCells":"Celle richieste","artifactBytes":"byte","artifactMore":"+{count} altri","dataPosture":"Percorsi dati dei processi di supporto","dataPostureScope":"Percorsi opzionali usati dai processi di supporto. Non descrive n\u00e9 conta il contesto inviato da Codex al modello.","localFiles":"File letti dai processi di supporto","connectors":"Connettori opzionali","uploads":"Percorsi di upload opzionali","remoteSql":"SQL remoto dei processi di supporto","hostedNotebook":"Notebook hosted dei processi di supporto","executionProvenance":"Provenienza esecuzione","executionPending":"La traccia di esecuzione appare dopo che run_intake.json registra il run locale.","traceStepSingular":"passo","traceStepPlural":"passi","executionLocation":"Luogo","executionKind":"Tipo","executionStatus":"Stato","executionCommand":"Comando","executionInputs":"Input","executionOutputs":"Output","localExecution":"Locale","remoteExecution":"Remoto","reviewSafeguards":"Garanzie revisione","safeguardsReady":"Pronto","safeguardsCheck":"Da verificare","safeguardsWarning":"Attenzione","safeguardLocalExecution":"Esecuzione degli script helper","safeguardExternalRoute":"Percorsi opzionali oltre Codex","safeguardBoundedPayload":"Payload limitato","safeguardDecisionPersistence":"Persistenza decisioni","safeguardFinalArtifacts":"Artefatti finali","externalRouteUsed":"Percorso usato e registrato","externalRouteNotUsed":"Nessun percorso opzionale usato","payloadItemsCount":"{count} elementi","finalArtifactsDeclared":"{count} output","finalArtifactsMissing":"Nessun output dichiarato","executionTraceMissing":"Traccia mancante","localExecutionDeclared":"Script eseguito nel workspace locale","remoteExecutionDeclared":"Script eseguito su un percorso esterno","persistenceAvailable":"Salva/applica disponibile","persistenceFallback":"Solo copia JSON","notDeclared":"Non dichiarato","noneValue":"Nessuno","blockers":"Blocchi","caveats":"Caveat","nextActions":"Azioni successive","yes":"S\u00ec","no":"No","editRequired":"Valore modificato richiesto per {item}.","saving":"Salvataggio...","savingApplying":"Salvataggio e applicazione...","decisionsSaved":"Decisioni salvate.","decisionsApplied":"Decisioni applicate.","toolCopied":"Bridge strumenti non disponibile. JSON copiato.","toolCopiedApply":"Bridge strumenti non disponibile. JSON copiato; applicare richiede MCP.","jsonDownloaded":"JSON scaricato.","jsonCopied":"JSON copiato.","emptyJsonCopied":"JSON vuoto copiato.","sampleLoaded":"Esempio caricato.","saveFailed":"Salvataggio decisioni non riuscito.","applyFailed":"Applicazione decisioni non riuscita."}},"fr":{"actions":{"accept":"Accepter","reject":"Rejeter","edit":"Modifier","mark_unclear":"Marquer incertain","request_more_documents":"Demander documents","skip":"Ignorer"},"statuses":{"accepted":"Accepte","rejected":"Rejete","edited":"Modifie","needs_evidence":"Justificatif requis","skipped":"Ignore","pending":"En attente","pending_review":"Revue en attente","partial_review":"Revue partielle","reviewed":"Revu","ready":"Pret","ready_for_review":"Pret pour revue","empty":"Vide","written":"Ecrit","written_pending_review":"Ecrit, revue en attente","final_ready":"Final pret","blocked":"Bloque","partial_review_applied":"Applique partiellement"},"metrics":{"items":"Elements","issues":"Points","evidence":"Justificatifs","artifacts":"Sorties","reviewed":"Revus"},"buttons":{"useRecommended":"Utiliser recommande","saveDecisions":"Enregistrer decisions","applyDecisions":"Appliquer decisions","copyJson":"Copier JSON","downloadJson":"Telecharger JSON","previewSample":"Voir exemple","copyEmpty":"Copier JSON vide"},"fields":{"type":"Type","recommended":"Recommande","decision":"Decision","source":"Source","output":"Sortie","reviewerNote":"Note du reviseur","editValue":"Valeur modifiee","requestedDocuments":"Documents demandes"},"text":{"loadingPayload":"Chargement du payload...","mcpWidget":"Widget MCP","reviewedSuffix":"d\u00e9cisions s\u00e9lectionn\u00e9es","decidedProgress":"{decided} / {total} decides","runLabel":"Run","noSourcePaths":"Aucun chemin source fourni","writesDecisions":"Ecrit ui_decisions.json","jsonFallback":"Fallback JSON","decisionImpact":"Impact decision","currentAction":"Action","writeMode":"Mode ecriture","writeTarget":"Cible ecriture","documentHint":"Document suggere","reviewStore":"Etat revue","currentDecisions":"Courantes","savedDecisions":"Enregistrees","appliedDecisions":"Appliquees","currentDecisionCount":"{count} courantes","savedDecisionCount":"{count} enregistrees","appliedDecisionCount":"{count} appliquees","unsavedDecisionCount":"{count} non enregistrees","inSync":"Synchronise","recoveredDraftCount":"{count} brouillon recupere","fallbackActive":"Fallback JSON actif","fallbackBody":"Copiez ou telechargez ui_decisions.json. Utilisez la surface MCP ou serveur local pour enregistrer et appliquer les decisions.","applyRequiresMcp":"L'application requiert la surface MCP ou serveur local.","lastError":"Derniere erreur","recoveryAction":"Recuperation","retryWithMcp":"Reessayez depuis la surface MCP ou serveur local, ou copiez/telechargez le JSON decisions pour handoff.","toolLabel":"Outil","saveFailedTitle":"Enregistrement non persiste","applyFailedTitle":"Application non persistee","mcpWriteMode":"Enregistrer/appliquer MCP","copyOnlyMode":"Copie JSON seule","noDecisionYet":"Aucune decision","revisionOnly":"Fichier de revision seul jusqu'a mise a jour native par le workflow","notTargeted":"Aucune cible directe","targetRecord":"enregistrement","targetField":"champ","targetRecords":"enregistrements","all":"Tous","allStates":"Tous statuts","artifactState":"Etat sortie","reviewQueue":"File de revue","evidenceDecision":"Justificatif et decision","noPayloadTitle":"Aucun payload de revue charge","noPayloadBody":"Ce widget attend un appel MCP avec review_payload.json. Ouvrir le HTML directement n'inclut pas les donnees du run.","noFilterMatch":"Aucun element ne correspond aux filtres.","noFields":"Aucun champ.","detail":"Detail","reviewEvidence":"Revoyez le justificatif et saisissez la decision.","evidenceTrail":"Trace justificative","waitingTitle":"En attente des donnees de revue","waitingBody":"Rendez ce widget depuis l'outil MCP pour charger les elements, justificatifs et sorties.","selectItem":"Selectionnez un element.","readyDecision":"Pret pour la decision du reviseur.","finalOutputs":"Sorties finales","fileSingular":"fichier","filePlural":"fichiers","output":"Sortie","outputsPending":"Les sorties apparaissent apres l'ecriture des artefacts finaux.","artifactRows":"Lignes","artifactMinRows":"Min lignes","artifactSheets":"Feuilles","artifactHeaders":"En-tetes","artifactCells":"Cellules","artifactText":"Texte","artifactColumns":"Colonnes","artifactRecords":"Enregistrements","artifactQaDetails":"Details verification","artifactQaChecks":"Controles QA","artifactRequiredText":"Texte requis","artifactRequiredColumns":"Colonnes requises","artifactRequiredSheets":"Feuilles requises","artifactRequiredHeaders":"En-tetes requis","artifactRequiredCells":"Cellules requises","artifactBytes":"octets","artifactMore":"+{count} autres","dataPosture":"Flux de donn\u00e9es des processus auxiliaires","dataPostureScope":"Flux optionnels utilis\u00e9s par les processus auxiliaires. Ceci ne d\u00e9crit ni ne compte le contexte envoy\u00e9 par Codex au mod\u00e8le.","localFiles":"Fichiers lus par les processus auxiliaires","connectors":"Connecteurs optionnels","uploads":"Flux de t\u00e9l\u00e9versement optionnels","remoteSql":"SQL distant des processus auxiliaires","hostedNotebook":"Notebook h\u00e9berg\u00e9 des processus auxiliaires","executionProvenance":"Provenance execution","executionPending":"La trace d'execution apparait apres l'enregistrement du run local dans run_intake.json.","traceStepSingular":"etape","traceStepPlural":"etapes","executionLocation":"Lieu","executionKind":"Type","executionStatus":"Statut","executionCommand":"Commande","executionInputs":"Entrees","executionOutputs":"Sorties","localExecution":"Locale","remoteExecution":"Distante","reviewSafeguards":"Garde-fous revue","safeguardsReady":"Pret","safeguardsCheck":"A verifier","safeguardsWarning":"Attention","safeguardLocalExecution":"Execution des scripts auxiliaires","safeguardExternalRoute":"Parcours optionnels hors de Codex","safeguardBoundedPayload":"Payload borne","safeguardDecisionPersistence":"Persistance decisions","safeguardFinalArtifacts":"Artefacts finaux","externalRouteUsed":"Parcours utilise et enregistre","externalRouteNotUsed":"Aucun parcours optionnel utilise","payloadItemsCount":"{count} elements","finalArtifactsDeclared":"{count} sorties","finalArtifactsMissing":"Aucune sortie declaree","executionTraceMissing":"Trace manquante","localExecutionDeclared":"Script execute dans le workspace local","remoteExecutionDeclared":"Script execute via un parcours externe","persistenceAvailable":"Enregistrement/application disponible","persistenceFallback":"Copie JSON seule","notDeclared":"Non declare","noneValue":"Aucun","blockers":"Blocages","caveats":"Caveats","nextActions":"Actions suivantes","yes":"Oui","no":"Non","editRequired":"Valeur modifiee requise pour {item}.","saving":"Enregistrement...","savingApplying":"Enregistrement et application...","decisionsSaved":"Decisions enregistrees.","decisionsApplied":"Decisions appliquees.","toolCopied":"Bridge outil indisponible. JSON copie.","toolCopiedApply":"Bridge outil indisponible. JSON copie; appliquer requiert MCP.","jsonDownloaded":"JSON telecharge.","jsonCopied":"JSON copie.","emptyJsonCopied":"JSON vide copie.","sampleLoaded":"Exemple charge.","saveFailed":"Echec d'enregistrement des decisions.","applyFailed":"Echec d'application des decisions."}},"de":{"actions":{"accept":"Akzeptieren","reject":"Ablehnen","edit":"Bearbeiten","mark_unclear":"Unklar markieren","request_more_documents":"Dokumente anfordern","skip":"Ueberspringen"},"statuses":{"accepted":"Akzeptiert","rejected":"Abgelehnt","edited":"Bearbeitet","needs_evidence":"Nachweis erforderlich","skipped":"Uebersprungen","pending":"Ausstehend","pending_review":"Review ausstehend","partial_review":"Teilreview","reviewed":"Geprueft","ready":"Bereit","ready_for_review":"Bereit fuer Review","empty":"Leer","written":"Geschrieben","written_pending_review":"Geschrieben, Review ausstehend","final_ready":"Final bereit","blocked":"Blockiert","partial_review_applied":"Teilweise angewendet"},"metrics":{"items":"Elemente","issues":"Punkte","evidence":"Nachweise","artifacts":"Ausgaben","reviewed":"Geprueft"},"buttons":{"useRecommended":"Empfehlungen nutzen","saveDecisions":"Entscheidungen speichern","applyDecisions":"Entscheidungen anwenden","copyJson":"JSON kopieren","downloadJson":"JSON herunterladen","previewSample":"Beispiel anzeigen","copyEmpty":"Leeres JSON kopieren"},"fields":{"type":"Typ","recommended":"Empfohlen","decision":"Entscheidung","source":"Quelle","output":"Ausgabe","reviewerNote":"Pruefernotiz","editValue":"Bearbeiteter Wert","requestedDocuments":"Angeforderte Dokumente"},"text":{"loadingPayload":"Payload wird geladen...","mcpWidget":"MCP-Widget","reviewedSuffix":"Entscheidungen ausgew\u00e4hlt","decidedProgress":"{decided} / {total} entschieden","runLabel":"Run","noSourcePaths":"Keine Quellpfade angegeben","writesDecisions":"Schreibt ui_decisions.json","jsonFallback":"JSON-Fallback","decisionImpact":"Entscheidungswirkung","currentAction":"Aktion","writeMode":"Schreibmodus","writeTarget":"Schreibziel","documentHint":"Dokumenthinweis","reviewStore":"Review-Stand","currentDecisions":"Aktuell","savedDecisions":"Gespeichert","appliedDecisions":"Angewendet","currentDecisionCount":"{count} aktuell","savedDecisionCount":"{count} gespeichert","appliedDecisionCount":"{count} angewendet","unsavedDecisionCount":"{count} ungespeichert","inSync":"Synchron","recoveredDraftCount":"{count} Entwurf wiederhergestellt","fallbackActive":"JSON-Fallback aktiv","fallbackBody":"Kopieren oder laden Sie ui_decisions.json herunter. Nutzen Sie die MCP- oder lokale Server-Reviewflaeche zum Speichern und Anwenden.","applyRequiresMcp":"Anwenden erfordert die MCP- oder lokale Server-Reviewflaeche.","lastError":"Letzter Fehler","recoveryAction":"Wiederherstellung","retryWithMcp":"Erneut ueber die MCP- oder lokale Server-Reviewflaeche versuchen, oder Entscheidungs-JSON fuer Handoff kopieren/herunterladen.","toolLabel":"Tool","saveFailedTitle":"Speichern nicht persistiert","applyFailedTitle":"Anwenden nicht persistiert","mcpWriteMode":"MCP speichern/anwenden","copyOnlyMode":"Nur JSON kopieren","noDecisionYet":"Keine Entscheidung","revisionOnly":"Nur Revisionsdatei, bis der Workflow ein natives Artefakt aktualisiert","notTargeted":"Kein direktes Artefaktziel","targetRecord":"Record","targetField":"Feld","targetRecords":"Records","all":"Alle","allStates":"Alle Status","artifactState":"Ausgabestatus","reviewQueue":"Review-Warteschlange","evidenceDecision":"Nachweis und Entscheidung","noPayloadTitle":"Kein Review-Payload geladen","noPayloadBody":"Dieses Widget wartet auf einen MCP-Renderaufruf mit review_payload.json. Direktes Oeffnen der HTML-Datei enthaelt keine Run-Daten.","noFilterMatch":"Kein Element entspricht den aktuellen Filtern.","noFields":"Keine Felder.","detail":"Detail","reviewEvidence":"Nachweis pruefen und Entscheidung erfassen.","evidenceTrail":"Nachweisverlauf","waitingTitle":"Warten auf Review-Daten","waitingBody":"Rendern Sie dieses Widget ueber das MCP-Tool, um Review-Elemente, Nachweise und Ausgaben zu laden.","selectItem":"Review-Element auswaehlen.","readyDecision":"Bereit fuer Prueferentscheidung.","finalOutputs":"Finale Ausgaben","fileSingular":"Datei","filePlural":"Dateien","output":"Ausgabe","outputsPending":"Ausgaben erscheinen, nachdem die finalen Artefakte geschrieben wurden.","artifactRows":"Zeilen","artifactMinRows":"Mindestzeilen","artifactSheets":"Blaetter","artifactHeaders":"Kopfzeilen","artifactCells":"Zellen","artifactText":"Text","artifactColumns":"Spalten","artifactRecords":"Records","artifactQaDetails":"Pruefdetails","artifactQaChecks":"QA-Pruefungen","artifactRequiredText":"Erforderlicher Text","artifactRequiredColumns":"Erforderliche Spalten","artifactRequiredSheets":"Erforderliche Blaetter","artifactRequiredHeaders":"Erforderliche Kopfzeilen","artifactRequiredCells":"Erforderliche Zellen","artifactBytes":"Bytes","artifactMore":"+{count} weitere","dataPosture":"Datenwege der Hilfsprozesse","dataPostureScope":"Optionale Wege der Hilfsprozesse. Dies beschreibt oder z\u00e4hlt nicht den von Codex an das Modell gesendeten Kontext.","localFiles":"Von Hilfsprozessen gelesene Dateien","connectors":"Optionale Konnektoren","uploads":"Optionale Upload-Wege","remoteSql":"Remote-SQL der Hilfsprozesse","hostedNotebook":"Gehostetes Notebook der Hilfsprozesse","executionProvenance":"Ausfuehrungsnachweis","executionPending":"Die Ausfuehrungsspur erscheint, nachdem run_intake.json den lokalen Run erfasst.","traceStepSingular":"Schritt","traceStepPlural":"Schritte","executionLocation":"Ort","executionKind":"Typ","executionStatus":"Status","executionCommand":"Befehl","executionInputs":"Eingaben","executionOutputs":"Ausgaben","localExecution":"Lokal","remoteExecution":"Remote","reviewSafeguards":"Review-Schutzmassnahmen","safeguardsReady":"Bereit","safeguardsCheck":"Pruefen","safeguardsWarning":"Achtung","safeguardLocalExecution":"Ausfuehrung der Hilfsskripte","safeguardExternalRoute":"Optionale Wege ausserhalb von Codex","safeguardBoundedPayload":"Begrenzter Payload","safeguardDecisionPersistence":"Entscheidungspersistenz","safeguardFinalArtifacts":"Finale Artefakte","externalRouteUsed":"Weg verwendet und erfasst","externalRouteNotUsed":"Kein optionaler Weg verwendet","payloadItemsCount":"{count} Elemente","finalArtifactsDeclared":"{count} Ausgaben","finalArtifactsMissing":"Keine Ausgaben deklariert","executionTraceMissing":"Spur fehlt","localExecutionDeclared":"Hilfsskript im lokalen Workspace","remoteExecutionDeclared":"Hilfsskript ueber externen Pfad","persistenceAvailable":"Speichern/Anwenden verfuegbar","persistenceFallback":"Nur JSON kopieren","notDeclared":"Nicht deklariert","noneValue":"Keine","blockers":"Blocker","caveats":"Hinweise","nextActions":"Naechste Schritte","yes":"Ja","no":"Nein","editRequired":"Bearbeiteter Wert fuer {item} erforderlich.","saving":"Speichern...","savingApplying":"Speichern und anwenden...","decisionsSaved":"Entscheidungen gespeichert.","decisionsApplied":"Entscheidungen angewendet.","toolCopied":"Tool-Bridge nicht verfuegbar. JSON kopiert.","toolCopiedApply":"Tool-Bridge nicht verfuegbar. JSON kopiert; Anwenden erfordert MCP.","jsonDownloaded":"JSON heruntergeladen.","jsonCopied":"JSON kopiert.","emptyJsonCopied":"Leeres JSON kopiert.","sampleLoaded":"Beispiel geladen.","saveFailed":"Speichern der Entscheidungen fehlgeschlagen.","applyFailed":"Anwenden der Entscheidungen fehlgeschlagen."}}};
const GROUP_TEXT = {"es":{"Journal Entry":"Asiento contable","Support Document":"Documento justificativo","Finding":"Hallazgo","Claim":"Afirmaci\u00f3n","Citation / Source":"Cita / fuente","Source Support":"Respaldo de la fuente","Validation Finding":"Hallazgo de validaci\u00f3n","Client Document":"Documento del cliente","Required Evidence":"Evidencia requerida","Memo / Email Impact":"Impacto en informe / correo","Reconciliation Row":"L\u00ednea de conciliaci\u00f3n","Evidence":"Evidencia","Exception":"Excepci\u00f3n","Extracted Journal Row":"L\u00ednea contable extra\u00edda","Sampling Basis":"Base de muestreo","Output Trace":"Trazabilidad de salida","Bank Statement":"Extracto bancario","Match Result":"Resultado de conciliaci\u00f3n","Draft Section":"Secci\u00f3n del borrador","Source Table":"Tabla fuente","Revision Note":"Nota de revisi\u00f3n","Prompt Section":"Secci\u00f3n del prompt","Research Scope":"Alcance de la investigaci\u00f3n","Required Revision":"Revisi\u00f3n requerida","Plan Section":"Secci\u00f3n del plan","Accounting Support":"Respaldo contable","Tie-Out Finding":"Hallazgo de cuadre"},"it":{"Journal Entry":"Scrittura contabile","Support Document":"Documento di supporto","Finding":"Rilievo","Claim":"Claim","Citation / Source":"Citazione / fonte","Source Support":"Supporto fonte","Validation Finding":"Rilievo validazione","Client Document":"Documento cliente","Required Evidence":"Evidenza richiesta","Memo / Email Impact":"Impatto memo / email","Reconciliation Row":"Riga riconciliazione","Evidence":"Evidenza","Exception":"Eccezione","Extracted Journal Row":"Riga contabile estratta","Sampling Basis":"Base campionamento","See the sampling control for method and population.":"Metodo e popolazione sono nella voce Parametri campione.","Output Trace":"Traccia output","Bank Statement":"Estratto conto","Match Result":"Esito match","Draft Section":"Sezione bozza","No draft text.":"Nessun testo di bozza per questa voce.","No source table fields.":"Nessuna tabella fonte indicata per questa voce.","No revision note.":"Nessuna nota di revisione.","Source Table":"Tabella fonte","Revision Note":"Nota revisione","Prompt Section":"Sezione prompt","Research Scope":"Ambito ricerca","Required Revision":"Revisione richiesta","Plan Section":"Sezione piano","Accounting Support":"Supporto contabile","Tie-Out Finding":"Rilievo tie-out"},"fr":{"Journal Entry":"Ecriture comptable","Support Document":"Document justificatif","Finding":"Constat","Claim":"Assertion","Citation / Source":"Citation / source","Source Support":"Appui de la source","Validation Finding":"Constat de validation","Client Document":"Document client","Required Evidence":"Justificatif requis","Memo / Email Impact":"Impact memo / email","Reconciliation Row":"Ligne de rapprochement","Evidence":"Justificatif","Exception":"Exception","Extracted Journal Row":"Ligne d'ecriture extraite","Sampling Basis":"Base d'echantillonnage","Output Trace":"Trace de sortie","Bank Statement":"Releve bancaire","Match Result":"Resultat de rapprochement","Draft Section":"Section brouillon","Source Table":"Table source","Revision Note":"Note de revue","Prompt Section":"Section prompt","Research Scope":"Perimetre de recherche","Required Revision":"Revision requise","Plan Section":"Section du plan","Accounting Support":"Support comptable","Tie-Out Finding":"Constat de rapprochement"},"de":{"Journal Entry":"Buchung","Support Document":"Beleg","Finding":"Feststellung","Claim":"Aussage","Citation / Source":"Zitat / Quelle","Source Support":"Quellennachweis","Validation Finding":"Validierungsfeststellung","Client Document":"Mandantendokument","Required Evidence":"Erforderlicher Nachweis","Memo / Email Impact":"Memo-/E-Mail-Auswirkung","Reconciliation Row":"Abstimmungszeile","Evidence":"Nachweis","Exception":"Ausnahme","Extracted Journal Row":"Extrahierte Buchungszeile","Sampling Basis":"Stichprobenbasis","Output Trace":"Ausgabeprotokoll","Bank Statement":"Kontoauszug","Match Result":"Abstimmungsergebnis","Draft Section":"Entwurfsabschnitt","Source Table":"Quelltabelle","Revision Note":"Review-Notiz","Prompt Section":"Prompt-Abschnitt","Research Scope":"Rechercheumfang","Required Revision":"Erforderliche Ueberarbeitung","Plan Section":"Planabschnitt","Accounting Support":"Buchhaltungsnachweis","Tie-Out Finding":"Abgleichsfeststellung"}};
const DEMO_PAYLOAD = {
widget_type: CONFIG.widgetType,
run_intake: {
schema_version: "1.0",
plugin: CONFIG.plugin,
workflow: CONFIG.plugin,
run_id: "sample-review",
output_dir: "/tmp/" + CONFIG.plugin + "/sample",
input_paths: ["input.xlsx", "evidence/"],
language: "en",
data_posture: {
local_files_read: ["input.xlsx", "evidence/"],
external_connectors_used: [],
upload_paths_used: [],
remote_sql_execution_used: false,
hosted_notebook_execution_used: false,
},
execution_trace: [
{
step_id: CONFIG.plugin + "_sample_run",
kind: "deterministic_review_session",
status: "passed",
execution_location: "local_codex_workspace",
command: ["python", "plugins/" + CONFIG.plugin + "/scripts/review_session.py"],
inputs: ["input.xlsx", "evidence/"],
outputs: ["review_payload.json", "review_summary.md", "evidence_workbook.xlsx", "final_artifacts.json"],
},
],
status: "ready_for_review",
},
review_payload: {
schema_version: "1.0",
plugin: CONFIG.plugin,
workflow: CONFIG.plugin,
run_id: "sample-review",
review_type: CONFIG.demo.review_type,
status: "ready_for_review",
items: CONFIG.demo.items,
item_count: CONFIG.demo.items.length,
summary: { result_row_count: CONFIG.demo.items.length, issue_count: CONFIG.demo.items.filter((item) => item.recommended_action !== "accept").length, artifact_count: 2 },
},
ui_decisions: { schema_version: "1.0", plugin: CONFIG.plugin, workflow: CONFIG.plugin, run_id: "sample-review", review_payload_path: "review_payload.json", decisions: [], decision_count: 0, status: "pending_review" },
final_artifacts: { schema_version: "1.0", plugin: CONFIG.plugin, workflow: CONFIG.plugin, run_id: "sample-review", outputs: [
{ path: "review_payload.json", kind: "json", status: "written", row_count: CONFIG.demo.items.length, records_key: "items", required_columns: ["id", "item_type", "title"], qa_checks: ["json_parse", "row_count", "required_columns"] },
{ path: "review_summary.md", kind: "md", status: "written", required_text: [CONFIG.reviewTitle, "local_codex_workspace", "Sample payload is bounded for review."], qa_checks: ["nonempty_text", "required_text"] },
{ path: "evidence_workbook.xlsx", kind: "xlsx", status: "written", required_sheets: ["summary"], required_sheet_headers: { summary: ["item_id", "recommended_action", "status"] }, required_cells: { summary: { A1: "item_id", A2: CONFIG.demo.items[0]?.id || "sample-1", B1: "recommended_action", B2: CONFIG.demo.items[0]?.recommended_action || "accept" } }, qa_checks: ["office_zip", "workbook_xml", "required_sheets", "required_sheet_headers", "required_cells"] },
{ path: "ui_decisions.json", kind: "json", status: "pending_review", records_key: "decisions", min_rows: 1 },
], caveats: ["Sample payload is bounded for review."], next_actions: ["Save or apply decisions from the widget before treating outputs as final."], status: "written_pending_review" },
decision_policy: { save_tool: CONFIG.saveTool, apply_tool: CONFIG.applyTool, can_persist: false, fallback: "copy_json" },
};
const FALLBACK = {
widget_type: CONFIG.widgetType,
run_intake: null,
review_payload: { schema_version: "1.0", plugin: CONFIG.plugin, workflow: CONFIG.plugin, run_id: "preview", status: "empty", items: [], item_count: 0, summary: {} },
ui_decisions: null,
final_artifacts: null,
decision_policy: { save_tool: CONFIG.saveTool, apply_tool: CONFIG.applyTool, can_persist: false, fallback: "copy_json" },
};
const state = { payload: FALLBACK, selectedType: "all", selectedState: "all", selectedId: null, query: "", decisions: {}, recovery: { savedDecisionCount: 0, draftDecisionCount: 0, lastIssue: null } };
function pickPayload(raw) {
if (!raw || typeof raw !== "object") return null;
if (raw.widget_type === CONFIG.widgetType || raw.review_payload || raw.ui_decisions || raw.final_artifacts || raw.applied_decisions) return raw;
for (const key of ["widget_payload", "_meta", "toolResponseMetadata", "mcp_tool_result", "call_tool_result", "structuredContent", "toolOutput"]) {
const selected = pickPayload(raw[key]);
if (selected) return selected;
}
return null;
}
function toolOutput() {
if (new URLSearchParams(window.location.search).has("demo")) return DEMO_PAYLOAD;
const host = window.openai || {};
return pickPayload(host.toolResponseMetadata) || pickPayload(host.toolOutput) || pickPayload(host.structuredContent) || pickPayload(host) || FALLBACK;
}
function esc(value) {
return String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
}
const IT_METADATA_LABELS = {"matched_pair": "Movimenti abbinati", "unmatched_bank": "Movimento bancario non abbinato", "unmatched_journal": "Registrazione non abbinata", "entry": "Registrazione", "counterparty": "Controparte", "amount": "Importo", "date": "Data", "description": "Descrizione", "status": "Stato", "reference": "Riferimento", "match_method": "Metodo di abbinamento", "bank_amount": "Importo bancario", "journal_amount": "Importo contabile", "difference": "Differenza", "source_path": "Documento di origine", "output_path": "Documento prodotto", "artifact": "Documento prodotto", "report_artifact": "Report", "chart_artifact": "Grafico", "evidence": "Evidenza", "account": "Conto", "currency": "Valuta", "debit": "Dare", "credit": "Avere", "invoice_number": "Numero fattura", "exact": "Esatto", "matched": "Abbinato", "unmatched": "Non abbinato", "sampling_control": "Parametri campione", "sampled_entry": "Riga estratta", "sample_artifact": "Campione", "review_artifact": "Tracciato", "entry_date": "Data registrazione", "movement_number": "Registrazione", "line_number": "Riga", "line_desc": "Descrizione", "amount_signed": "Importo con segno", "amount_abs": "Importo assoluto", "method": "Metodo", "sample_size": "Righe estratte", "requested_size": "Righe richieste", "population_size_after_filters": "Righe nella popolazione", "seed": "Seme casuale", "source_file": "File sorgente", "source_row": "Riga sorgente", "needs_review": "Da rivedere", "reported_increment": "Precisione importi", "filters": "Filtri", "population_proof": "Controlli sulla popolazione", "sampling_parameters": "Parametri di estrazione", "kind": "Tipo", "manual_review": "Da rivedere", "matched_support": "Documento abbinato", "support_type": "Tipo documento", "support_artifact_id": "Identificativo tecnico del documento", "support_match_status": "Abbinamento", "support_match_signals": "Elementi di confronto", "amount_found": "Importo nel documento", "date_found": "Data nel documento", "checks_run": "Controlli eseguiti", "mismatches": "Punti da chiarire", "review_notes": "Note di revisione", "evidence_facts": "Dati estratti dal documento", "professional_conclusion": "Conclusione professionale", "assurance_gate_status": "Stato dei controlli", "deterministic_checks": "Confronti automatici", "value": "Valore", "pending_review": "In attesa di revisione", "fatturapa_xml": "Fattura XML", "party_perimeter_requires_review": "Soggetto da confermare", "direction_requires_review": "Segno contabile da confermare", "missing_direction": "Confronto del segno non ancora concluso", "reconciliation_failed": "Confronto da completare", "semantic_review_withheld": "Giudizio professionale in attesa", "party_perimeter": "Soggetto", "report_section": "Sezione del report", "table_evidence": "Tabella di supporto", "review_issue": "Punto da rivedere", "section": "Sezione", "draft_text": "Testo della bozza", "codex_comment": "Testo attuale", "preview_rows": "Anteprima delle righe", "sheet_name": "Foglio", "source_table": "Tabella fonte", "metric": "Misura", "period": "Periodo", "reason": "Motivo", "blocker": "Impedimento", "proposed_fix": "Modifica proposta"};
function humanize(value) {
const key = String(value || "");
if (activeLanguage() === "it" && IT_METADATA_LABELS[key]) return IT_METADATA_LABELS[key];
return key.replace(/[_-]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
}
function formatValue(value) {
if (value == null || value === "") return "";
if (typeof value === "boolean") return value ? uiText("yes", "Yes") : uiText("no", "No");
if (typeof value === "number") return Number.isInteger(value) ? String(value) : value.toLocaleString(undefined, { maximumFractionDigits: 2 });
if (typeof value === "object") return JSON.stringify(value);
if (activeLanguage() === "it" && CONFIG.plugin === "check-entries") return String(value).split(/([,;])/).map((part) => IT_METADATA_LABELS[part] || part).join("");
return String(value);
}
function reviewPayload() { return state.payload.review_payload || FALLBACK.review_payload; }
function items() { return Array.isArray(reviewPayload().items) ? reviewPayload().items : []; }
function normalizeLanguage(value) {
const code = String(value || "").trim().toLowerCase().replace("_", "-").split("-")[0];
return Object.prototype.hasOwnProperty.call(UI_TEXT, code) ? code : "en";
}
function activeLanguage() {
const runIntake = state.payload.run_intake || {};
const payload = reviewPayload();
return normalizeLanguage(runIntake.language || runIntake.locale || payload.summary?.language || payload.language || "en");
}
function textGroup(group) {
const lang = activeLanguage();
return UI_TEXT[lang]?.[group] || UI_TEXT.en[group] || {};
}
function ui(group, key, fallback = "") {
return textGroup(group)[key] || UI_TEXT.en[group]?.[key] || fallback || key;
}
function uiText(key, fallback = "") { return ui("text", key, fallback); }
function workflowText(key, fallback = "") {
const lang = activeLanguage();
return CONFIG.localized?.[lang]?.[key] || CONFIG.localized?.en?.[key] || CONFIG[key] || fallback;
}
function groupTitle(group) {
const lang = activeLanguage();
return GROUP_TEXT[lang]?.[group.title] || group.title || uiText("detail", "Detail");
}
function groupEmpty(group) {
const lang = activeLanguage();
return GROUP_TEXT[lang]?.[group.empty] || group.empty || uiText("noFields", "No fields.");
}
function itemById(itemId) { return items().find((item) => item.id === itemId) || null; }
function decisionFor(itemId) { const decision = state.decisions[itemId]; return decision && itemById(itemId) ? decision : null; }
function validDecisionCount() { return Object.keys(state.decisions).filter((itemId) => decisionFor(itemId)).length; }
function actionLabel(action) { return ui("actions", action, ACTION_LABELS[action] || humanize(action || "Review")); }
function statusLabel(status) { return ui("statuses", status, humanize(status || "pending")); }
function actionClass(value) { return String(value || "review").replace(/[^a-z0-9_-]/gi, "_"); }
function statusFor(item) {
const decision = decisionFor(item.id);
if (decision?.action) return ACTION_STATUSES[decision.action] || decision.action;
const status = item.data?.status || item.status || "pending";
const statusText = String(status).toLowerCase();
if (["ok", "matched", "supported", "classified", "selected", "relevant", "drafted"].includes(status)) return "ready";
if (statusText.includes("missing") || statusText.includes("unmatched") || statusText.includes("mismatch") || statusText.includes("needs_evidence") || statusText.includes("blocked")) return "needs_evidence";
if (["missing", "unmatched", "mismatch", "variance", "needs_evidence"].includes(status)) return "needs_evidence";
return status;
}
function finalArtifacts() {
return state.payload.final_artifacts && typeof state.payload.final_artifacts === "object" ? state.payload.final_artifacts : {};
}
function artifactState() {
const artifacts = finalArtifacts();
return artifacts.review_status || artifacts.status || state.payload.applied_decisions?.application_status || state.payload.ui_decisions?.status || reviewPayload().status || "pending_review";
}
function blockerItemIds() {
return new Set((Array.isArray(finalArtifacts().blockers) ? finalArtifacts().blockers : [])
.map((blocker) => blocker?.item_id)
.filter(Boolean));
}
function reviewStateFor(item) {
if (blockerItemIds().has(item.id)) return "blocked";
const decision = decisionFor(item.id);
if (!decision?.action) return statusFor(item) === "needs_evidence" ? "needs_evidence" : "pending";
const status = ACTION_STATUSES[decision.action] || decision.action;
if (["needs_evidence", "rejected"].includes(status)) return "needs_evidence";
return "reviewed";
}
function reviewStateCounts() {
const counts = { all: items().length, pending: 0, reviewed: 0, needs_evidence: 0, blocked: 0 };
for (const item of items()) {
const stateName = reviewStateFor(item);
counts[stateName] = (counts[stateName] || 0) + 1;
}
return counts;
}
function supportLine(item) {
const data = item.data || {};
const parts = [data.source_title, data.citation, data.support, data.evidence, data.matched_pdf, data.bank_line, data.source_table, data.reason, data.review_notes, item.source_path || item.output_path].filter(Boolean);
return parts.slice(0, 3).join(" · ");
}
function filteredItems() {
const q = state.query.trim().toLowerCase();
return items().filter((item) => {
if (state.selectedType !== "all" && item.item_type !== state.selectedType) return false;
if (state.selectedState !== "all" && reviewStateFor(item) !== state.selectedState) return false;
return !q || JSON.stringify(item).toLowerCase().includes(q);
});
}
function metric(label, value) { return `<article class="metric"><span>${esc(label)}</span><strong>${esc(value)}</strong></article>`; }
function renderSummary() {
const payload = reviewPayload();
document.getElementById("technical-title").textContent = ({it: "Dettagli tecnici", en: "Technical details", fr: "Détails techniques", es: "Detalles técnicos", de: "Technische Details"})[activeLanguage()];
const summary = payload.summary || {};
const issueCount = summary.issue_count ?? items().filter((item) => item.recommended_action && item.recommended_action !== "accept").length;
const artifactCount = artifactRecords().length;
document.getElementById("subtitle").textContent = `${uiText("runLabel", "Run")} ${payload.run_id || "n/a"}`;
document.getElementById("status-pill").textContent = statusLabel(payload.status || "ready_for_review");
document.getElementById("summary").innerHTML = [
metric(ui("metrics", "items", "Items"), items().length),
metric(ui("metrics", "issues", "Issues"), issueCount),
metric(ui("metrics", "evidence", "Evidence"), items().filter((item) => Array.isArray(item.evidence) && item.evidence.length).length),
metric(ui("metrics", "artifacts", "Artifacts"), artifactCount),
metric(uiText("reviewedSuffix", "Decisions selected"), `${validDecisionCount()} / ${items().length}`),
].join("");
}
function renderProgress() {
const decided = validDecisionCount();
const total = items().length;
const percent = total ? Math.round((decided / total) * 100) : 0;
document.getElementById("decision-pill").textContent = `${decided} ${uiText("reviewedSuffix", "reviewed")}`;
document.getElementById("decision-progress").textContent = uiText("decidedProgress", "{decided} / {total} decided").replace("{decided}", decided).replace("{total}", total);
document.getElementById("decision-progress-fill").style.width = `${percent}%`;
document.getElementById("use-recommended").disabled = total === 0;
document.getElementById("save-decisions").disabled = total === 0;
document.getElementById("apply-decisions").disabled = total === 0;
renderReviewStore();
renderRecoveryPanel();
}
function countText(key, fallback, count) {
return uiText(key, fallback).replace("{count}", count);
}
function normalizedDecisionRecord(decision) {
if (!decision?.item_id || !decision?.action || !itemById(decision.item_id)) return null;
return {
item_id: String(decision.item_id),
action: String(decision.action),
reviewer_note: String(decision.reviewer_note || "").trim(),
edit_value: String(decision.edit_value || "").trim(),
requested_documents: Array.isArray(decision.requested_documents)
? decision.requested_documents.map((entry) => String(entry || "").trim()).filter(Boolean).sort()
: [],
};
}
function normalizedDecisionList(decisions) {
return (Array.isArray(decisions) ? decisions : [])
.map(normalizedDecisionRecord)
.filter(Boolean)
.sort((left, right) => left.item_id.localeCompare(right.item_id));
}
function decisionSignature(decisions) {
return JSON.stringify(normalizedDecisionList(decisions));
}
function decisionSignatureByItem(decisions) {
const signatures = new Map();
for (const decision of normalizedDecisionList(decisions)) {
signatures.set(decision.item_id, JSON.stringify(decision));
}
return signatures;
}
function changedDecisionCount(currentDecisions, baselineDecisions) {
const baseline = decisionSignatureByItem(baselineDecisions);
return normalizedDecisionList(currentDecisions).filter((decision) => baseline.get(decision.item_id) !== JSON.stringify(decision)).length;
}
function savedDecisionInputs() {
return normalizedDecisionList(state.payload.ui_decisions?.decisions);
}
function appliedDecisionInputs() {
return normalizedDecisionList(state.payload.applied_decisions?.decisions);
}
function reviewStoreItem(label, value, kind = "") {
return `<div class="review-store__item${kind ? ` is-${esc(kind)}` : ""}"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`;
}
function renderReviewStore() {
const node = document.getElementById("review-store");
const current = collectDecisionInputs();
const saved = savedDecisionInputs();
const applied = appliedDecisionInputs();
const currentSignature = decisionSignature(current);
const savedSignature = decisionSignature(saved);
const unsavedCount = currentSignature === savedSignature ? 0 : changedDecisionCount(current, saved);
const hasUnsaved = unsavedCount > 0;
const draftCount = state.recovery?.draftDecisionCount || 0;
const cells = [
reviewStoreItem(uiText("currentDecisions", "Current"), countText("currentDecisionCount", "{count} current", current.length), current.length ? "current" : "empty"),
reviewStoreItem(uiText("savedDecisions", "Saved"), countText("savedDecisionCount", "{count} saved", saved.length), saved.length ? "saved" : "empty"),
reviewStoreItem(uiText("appliedDecisions", "Applied"), countText("appliedDecisionCount", "{count} applied", applied.length), applied.length ? "applied" : "empty"),
reviewStoreItem(uiText("reviewStore", "Review state"), hasUnsaved ? countText("unsavedDecisionCount", "{count} unsaved", unsavedCount) : uiText("inSync", "In sync"), hasUnsaved ? "dirty" : "applied"),
];
if (draftCount) cells.push(reviewStoreItem(uiText("jsonFallback", "JSON fallback"), countText("recoveredDraftCount", "{count} recovered draft", draftCount), "dirty"));
node.innerHTML = cells.join("");
}
function canPersistThroughBridge() {
return Boolean(state.payload.decision_policy?.can_persist) && typeof window.openai?.callTool === "function";
}
function recoveryLine(label, value) {
if (!value) return "";
return `<div class="recovery-panel__line"><span>${esc(label)}</span><span>${esc(value)}</span></div>`;
}
function setRecoveryIssue(kind, title, body, toolName = "", errorMessage = "") {
state.recovery.lastIssue = { kind, title, body, toolName, errorMessage };
renderRecoveryPanel();
}
function clearRecoveryIssue() {
if (!state.recovery) state.recovery = {};
state.recovery.lastIssue = null;
renderRecoveryPanel();
}
function renderRecoveryPanel() {
const node = document.getElementById("recovery-panel");
if (!node) return;
const issue = state.recovery?.lastIssue || null;
const fallbackActive = items().length > 0 && !canPersistThroughBridge();
if (!issue && !fallbackActive) {
node.className = "recovery-panel";
node.innerHTML = "";
return;
}
const title = issue?.title || uiText("fallbackActive", "JSON fallback active");
const body = issue?.body || uiText("fallbackBody", "Copy or download ui_decisions.json. Use the MCP or local-server review surface to persist and apply decisions.");
const tool = issue?.toolName || state.payload.decision_policy?.save_tool || CONFIG.saveTool;
const error = issue?.errorMessage || "";
const lines = [
recoveryLine(uiText("recoveryAction", "Recovery"), body),
recoveryLine(uiText("toolLabel", "Tool"), tool),
recoveryLine(uiText("lastError", "Last error"), error),
].filter(Boolean).join("");
node.className = `recovery-panel is-visible${issue?.kind === "error" ? " is-error" : ""}`;
node.innerHTML = `<div class="recovery-panel__title">${esc(title)}</div><div class="recovery-panel__body">${lines}</div>`;
}
function postureValue(value, fallback) {
if (Array.isArray(value)) return value.length ? listSummary(value, 3) : fallback;
if (typeof value === "boolean") return uiText(value ? "yes" : "no", value ? "Yes" : "No");
if (value === null || value === undefined || value === "") return fallback;
return String(value);
}
function remoteSqlValue(posture, fallback) {
if (typeof posture.remote_sql_execution_used === "boolean") return postureValue(posture.remote_sql_execution_used, fallback);
if (posture.remote_sql_execution_used) return postureValue(posture.remote_sql_execution_used, fallback);
const notes = Array.isArray(posture.notes) ? posture.notes.join(" ").toLowerCase() : "";
if (notes.includes("remote sql") && notes.includes("no external")) return uiText("no", "No");
return fallback;
}
function hostedNotebookValue(posture, fallback) {
if (typeof posture.hosted_notebook_execution_used === "boolean") return postureValue(posture.hosted_notebook_execution_used, fallback);
if (posture.hosted_notebook_execution_used) return postureValue(posture.hosted_notebook_execution_used, fallback);
const notes = Array.isArray(posture.notes) ? posture.notes.join(" ").toLowerCase() : "";
if (notes.includes("hosted notebook") && notes.includes("no external")) return uiText("no", "No");
return fallback;
}
function postureItem(label, value) {
return `<div class="data-posture__item"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`;
}
function renderDataPosture() {
const node = document.getElementById("data-posture");
const posture = state.payload.run_intake?.data_posture;
if (!posture || typeof posture !== "object" || Array.isArray(posture)) {
node.innerHTML = "";
node.style.display = "none";
return;
}
const none = uiText("noneValue", "None");
const missing = uiText("notDeclared", "Not declared");
const cells = [
postureItem(uiText("localFiles", "Files read by helpers"), postureValue(posture.local_files_read, missing)),
postureItem(uiText("connectors", "Optional connectors"), postureValue(posture.external_connectors_used, none)),
postureItem(uiText("uploads", "Optional upload routes"), postureValue(posture.upload_paths_used, none)),
postureItem(uiText("remoteSql", "Helper remote SQL"), remoteSqlValue(posture, missing)),
postureItem(uiText("hostedNotebook", "Helper hosted notebook"), hostedNotebookValue(posture, missing)),
];
node.style.display = "";
const scope = uiText("dataPostureScope", "Optional routes used by helper execution. This does not describe or count the context sent by Codex to the model.");
node.innerHTML = `<div class="data-posture__title">${esc(uiText("dataPosture", "Helper execution routes"))}<small>${esc(scope)}</small></div><div class="data-posture__grid">${cells.join("")}</div>`;
}
function traceEntries() {
const trace = state.payload.run_intake?.execution_trace;
return Array.isArray(trace) ? trace.filter((step) => step && typeof step === "object").slice(-4) : [];
}
function traceValue(value, fallback = "") {
if (Array.isArray(value)) return value.length ? listSummary(value, 4) : fallback;
if (value && typeof value === "object") {
const locator = value.path || value.uri || value.id || value.name || "";
return locator ? String(locator) : JSON.stringify(value);
}
if (value === null || value === undefined || value === "") return fallback;
return String(value);
}
function traceCommandValue(value) {
if (Array.isArray(value)) return value.map((entry) => String(entry)).join(" ");
return traceValue(value, "");
}
function isRemoteExecutionLocation(value) {
return ["external_connector", "hosted_notebook", "remote_warehouse"].includes(String(value || ""));
}
function traceStepHtml(step) {
const location = traceValue(step.execution_location, uiText("notDeclared", "Not declared"));
const remote = isRemoteExecutionLocation(step.execution_location);
const badge = remote ? uiText("remoteExecution", "Remote") : uiText("localExecution", "Local");
const rows = [
[uiText("executionLocation", "Location"), location],
[uiText("executionKind", "Kind"), traceValue(step.kind, "")],
[uiText("executionStatus", "Status"), traceValue(step.status, "")],
[uiText("executionCommand", "Command"), traceCommandValue(step.command)],
[uiText("executionInputs", "Inputs"), traceValue(step.inputs, "")],
[uiText("executionOutputs", "Outputs"), traceValue(step.outputs, "")],
].filter(([, value]) => value);
return `<div class="execution-step"><div class="execution-step__title"><span>${esc(traceValue(step.step_id, step.kind || "step"))}</span><span class="execution-badge${remote ? " is-remote" : ""}">${esc(badge)}</span></div><div class="execution-kv">${rows.map(([label, value]) => `<span>${esc(label)}</span><span>${esc(value)}</span>`).join("")}</div></div>`;
}
function renderExecutionProvenance() {
const node = document.getElementById("execution-provenance");
if (!node) return;
const trace = traceEntries();
if (!trace.length) {
node.className = "execution-provenance";
node.innerHTML = "";
return;
}
const countLabel = `${trace.length} ${esc(trace.length === 1 ? uiText("traceStepSingular", "step") : uiText("traceStepPlural", "steps"))}`;
node.className = "execution-provenance is-visible";
node.innerHTML = `<div class="execution-header"><h2>${esc(uiText("executionProvenance", "Execution provenance"))}</h2><span>${countLabel}</span></div><div class="execution-grid">${trace.map(traceStepHtml).join("")}</div>`;
}
function replaceCountText(key, fallback, count) {
return uiText(key, fallback).replace("{count}", count);
}
function postureUsesExternal(value) {
if (Array.isArray(value)) return value.length > 0;
if (typeof value === "boolean") return value;
if (value && typeof value === "object") return Object.keys(value).length > 0;
const text = String(value || "").trim().toLowerCase();
return Boolean(text && !["false", "no", "none", "0"].includes(text));
}
function externalExecutionUsed() {
const posture = state.payload.run_intake?.data_posture || {};
return postureUsesExternal(posture.external_connectors_used)
|| postureUsesExternal(posture.external_routes_used)
|| postureUsesExternal(posture.upload_paths_used)
|| postureUsesExternal(posture.remote_sql_execution_used)
|| postureUsesExternal(posture.hosted_notebook_execution_used)
|| traceEntries().some((step) => isRemoteExecutionLocation(step.execution_location));
}
function safeguardBadge(kind) {
if (kind === "ok") return uiText("safeguardsReady", "Ready");
if (kind === "danger" || kind === "warning") return uiText("safeguardsWarning", "Review");
return uiText("safeguardsCheck", "Check");
}
function safeguardItemHtml(label, value, kind = "check") {
return `<article class="safeguard is-${esc(kind)}"><div class="safeguard__top"><strong>${esc(label)}</strong><span class="safeguard__badge">${esc(safeguardBadge(kind))}</span></div><p>${esc(value)}</p></article>`;
}
function safeguardRecords() {
const trace = traceEntries();
const hasRemoteTrace = trace.some((step) => isRemoteExecutionLocation(step.execution_location));
const externalUsed = externalExecutionUsed();
const payload = reviewPayload();
const declaredCount = Number(payload.item_count);
const actualCount = items().length;
const countMismatch = Number.isFinite(declaredCount) && declaredCount !== actualCount;
const outputCount = artifactRecords().length;
return [
{
label: uiText("safeguardLocalExecution", "Helper-script execution"),
value: trace.length
? (hasRemoteTrace ? uiText("remoteExecutionDeclared", "Helper used an external path") : uiText("localExecutionDeclared", "Helper ran in local workspace"))
: uiText("executionTraceMissing", "Trace missing"),
kind: trace.length ? (hasRemoteTrace ? "check" : "ok") : "warning",
},
{
label: uiText("safeguardExternalRoute", "Optional routes beyond Codex"),
value: externalUsed
? uiText("externalRouteUsed", "Route used and recorded")
: uiText("externalRouteNotUsed", "No optional route used"),
kind: externalUsed ? "check" : "ok",
},
{
label: uiText("safeguardBoundedPayload", "Bounded payload"),
value: replaceCountText("payloadItemsCount", "{count} items", actualCount),
kind: countMismatch ? "danger" : "ok",
},
{
label: uiText("safeguardDecisionPersistence", "Decision persistence"),
value: canPersistThroughBridge() ? uiText("persistenceAvailable", "Save/apply available") : uiText("persistenceFallback", "Copy JSON only"),
kind: canPersistThroughBridge() ? "ok" : "check",
},
{
label: uiText("safeguardFinalArtifacts", "Final artifacts"),
value: outputCount ? replaceCountText("finalArtifactsDeclared", "{count} outputs", outputCount) : uiText("finalArtifactsMissing", "No outputs declared"),
kind: outputCount ? "ok" : "warning",
},
];
}
function renderReviewSafeguards() {
const node = document.getElementById("review-safeguards");
if (!node) return;
if (!items().length) {
node.className = "safeguards";
node.innerHTML = "";
return;
}
const records = safeguardRecords();
const needsReview = records.some((record) => ["danger", "warning"].includes(record.kind));
const summary = needsReview ? uiText("safeguardsWarning", "Review") : uiText("safeguardsReady", "Ready");
node.className = "safeguards is-visible";
node.innerHTML = `<div class="safeguards__header"><h2>${esc(uiText("reviewSafeguards", "Review safeguards"))}</h2><span>${esc(summary)}</span></div><div class="safeguards__grid">${records.map((record) => safeguardItemHtml(record.label, record.value, record.kind)).join("")}</div>`;
}
function renderContext() {
const payload = reviewPayload();
const runIntake = state.payload.run_intake || {};
const sourcePaths = Array.isArray(payload.source_paths) ? payload.source_paths : runIntake.input_paths || [];
const canPersist = Boolean(state.payload.decision_policy?.can_persist);
document.getElementById("run-context").innerHTML = `<div><strong>${esc(workflowText("reviewTitle", CONFIG.reviewTitle))}</strong><span>${esc(sourcePaths.length ? sourcePaths.join(" · ") : uiText("noSourcePaths", "No source paths supplied"))}</span></div><div class="context-list"><span class="context-token">${esc(runIntake.language || payload.summary?.language || "en")}</span><span class="context-token">${canPersist ? uiText("writesDecisions", "Writes ui_decisions.json") : uiText("jsonFallback", "JSON fallback")}</span></div>`;
}
function renderTabs() {
const counts = new Map([["all", items().length]]);
for (const item of items()) counts.set(item.item_type, (counts.get(item.item_type) || 0) + 1);
document.getElementById("tabs").innerHTML = Array.from(counts.entries()).map(([type, count]) => `<button class="tab" data-type="${esc(type)}" aria-pressed="${type === state.selectedType}">${esc(type === "all" ? uiText("all", "All") : humanize(type))} (${count})</button>`).join("");
}
function stateChip(key, label, count) {
return `<button class="state-chip is-${esc(actionClass(key))}" data-state-filter="${esc(key)}" aria-pressed="${key === state.selectedState}"><span>${esc(label)}</span><strong>${esc(count)}</strong></button>`;
}
function renderStateStrip() {
const node = document.getElementById("state-strip");
const counts = reviewStateCounts();
const chips = [
stateChip("all", uiText("allStates", "All states"), counts.all),
stateChip("pending", statusLabel("pending"), counts.pending),
stateChip("reviewed", statusLabel("reviewed"), counts.reviewed),
stateChip("needs_evidence", statusLabel("needs_evidence"), counts.needs_evidence),
stateChip("blocked", statusLabel("blocked"), counts.blocked),
].join("");
const currentArtifactState = artifactState();
node.innerHTML = `<div class="state-filters">${chips}</div><div class="state-final is-${esc(actionClass(currentArtifactState))}"><span>${esc(uiText("artifactState", "Artifact state"))}</span><strong>${esc(statusLabel(currentArtifactState))}</strong></div>`;
}
function rowHtml(item) {
const decision = decisionFor(item.id);
const action = decision?.action || item.recommended_action || "review";
const status = statusFor(item);
const stateName = reviewStateFor(item);
return `<button class="row row--${esc(actionClass(stateName))}${item.id === state.selectedId ? " is-selected" : ""}" data-id="${esc(item.id)}"><span class="type">${esc(humanize(item.item_type))}</span><span><span class="title">${esc(item.title)}</span><span class="row-support">${esc(supportLine(item))}</span></span><span><span class="action action--${esc(actionClass(action))}">${esc(actionLabel(action))}</span><span class="row-status is-${esc(actionClass(stateName))}">${esc(statusLabel(stateName))}</span></span></button>`;
}
function emptyQueueHtml() {
return `<div class="empty-state"><strong>${esc(uiText("noPayloadTitle", "No review payload loaded"))}</strong><p>${esc(uiText("noPayloadBody", "This widget is waiting for an MCP render call with review_payload.json. Opening the HTML file directly does not include run data."))}</p><div class="empty-actions"><button class="button primary" type="button" data-load-demo="true">${esc(ui("buttons", "previewSample", "Preview sample review"))}</button><button class="button" type="button" id="copy-empty-contract">${esc(ui("buttons", "copyEmpty", "Copy empty decision JSON"))}</button></div></div>`;
}
function renderRows() {
const rows = filteredItems();
document.getElementById("count").textContent = `${rows.length} / ${items().length}`;
document.getElementById("rows").innerHTML = rows.length ? rows.map(rowHtml).join("") : (items().length ? `<div class="empty">${esc(uiText("noFilterMatch", "No item matches the current filters."))}</div>` : emptyQueueHtml());
if (!state.selectedId && rows[0]) state.selectedId = rows[0].id;
if (state.selectedId && !rows.some((item) => item.id === state.selectedId)) state.selectedId = rows[0]?.id || null;
renderDetails();
renderSummary();
renderProgress();
renderStateStrip();
}
const PANEL_KEYS = [
["claim", "prompt", "section", "entry", "document", "account", "counterparty", "amount", "status", "fiscal_year"],
["citation", "source_title", "source_url", "support", "evidence", "matched_pdf", "bank_line", "source_table", "requested_document"],
["severity", "reason", "finding", "mismatches", "review_notes", "status", "variance", "blocker"],
];
function entriesFor(record, keys) {
if (!record || typeof record !== "object" || Array.isArray(record)) return [];
return keys.filter((key) => Object.prototype.hasOwnProperty.call(record, key)).map((key) => [key, record[key]]).filter(([, value]) => value != null && value !== "");
}
function evidenceValueHtml(value) {
if (typeof value === "string" && value.length < 100000 && /^[{[]/.test(value.trim())) {
try { const parsed = JSON.parse(value); if (parsed && typeof parsed === "object") value = parsed; } catch (_) {}
}
if (value && typeof value === "object") {
const label = ({it: "Dettagli tecnici", en: "Technical details", fr: "Détails techniques", es: "Detalles técnicos", de: "Technische Details"})[activeLanguage()];
return `<details><summary>${esc(label)}</summary><pre style="white-space:pre-wrap;overflow-wrap:anywhere">${esc(JSON.stringify(value, null, 2))}</pre></details>`;
}
return esc(formatValue(value));
}
function kvHtml(record, keys) {
const entries = entriesFor(record, keys);
if (!entries.length) return `<div class="empty">${esc(uiText("noFields", "No fields."))}</div>`;
return `<div class="kv">${entries.map(([key, value]) => `<div>${esc(humanize(key))}</div><div>${CONFIG.plugin === "report-builder" && key === "preview_rows" ? sourcePreviewHtml(value) : evidenceValueHtml(value)}</div>`).join("")}</div>`;
}
function valueForField(item, field) {
const data = item.data || {};
const roots = [data, item, ...(Array.isArray(item.evidence) ? item.evidence : [])];
for (const root of roots) {
if (!root || typeof root !== "object") continue;
if (!Object.prototype.hasOwnProperty.call(root, field)) continue;
const value = root[field];
if (CONFIG.plugin === "report-builder" && field === "preview_rows" && Array.isArray(value)) return value;
if (value == null || value === "") continue;
if (Array.isArray(value)) return value.map(formatValue).join(", ");
if (typeof value === "object") return JSON.stringify(value);
return value;
}
return "";
}
function groupEntries(item, group) {
return (group.fields || [])
.map((field) => [field, valueForField(item, field)])
.filter(([, value]) => value != null && value !== "");
}
function sourcePreviewHtml(rows) {
if (!Array.isArray(rows) || !rows.length || !rows.every(row => row && typeof row === "object" && !Array.isArray(row))) return evidenceValueHtml(rows);
const columns = [...new Set(rows.flatMap(row => Object.keys(row)))];
return `<div style="overflow-x:auto"><table><thead><tr>${columns.map(key => `<th>${esc(key)}</th>`).join("")}</tr></thead><tbody>${rows.map(row => `<tr>${columns.map(key => `<td>${esc(row[key] ?? "")}</td>`).join("")}</tr>`).join("")}</tbody></table></div>`;
}
function workflowCardHtml(item, group) {
const entries = groupEntries(item, group);
const variant = group.variant || "default";
const body = entries.length
? `<div class="kv">${entries.map(([key, value]) => `<div>${esc(humanize(key))}</div><div>${CONFIG.plugin === "report-builder" && key === "preview_rows" ? sourcePreviewHtml(value) : evidenceValueHtml(value)}</div>`).join("")}</div>`
: `<div class="empty">${esc(groupEmpty(group))}</div>`;
return `<section class="workflow-card workflow-card--${esc(variant)}"><h5>${esc(groupTitle(group))}</h5>${body}</section>`;
}
function workflowDetailHtml(item) {
const groups = Array.isArray(CONFIG.detailGroups) && CONFIG.detailGroups.length
? CONFIG.detailGroups
: CONFIG.panels.map((title, index) => ({ title, variant: "default", fields: PANEL_KEYS[index] || [] }));
return `<div class="workflow-detail workflow-detail--${esc(CONFIG.detailMode || "review")}"><div class="workflow-detail__header"><strong>${esc(workflowText("reviewTitle", CONFIG.reviewTitle))}</strong><span>${esc(workflowText("detailHelp", uiText("reviewEvidence", "Review the evidence and capture the decision.")))}</span></div><div class="workflow-detail__grid">${groups.map((group) => workflowCardHtml(item, group)).join("")}</div></div>`;
}
function evidenceHtml(item) {
const evidence = Array.isArray(item.evidence) ? item.evidence : [];
if (!evidence.length) return "";
return `<div class="review-section"><h4>${esc(uiText("evidenceTrail", "Evidence trail"))}</h4><div class="evidence-list">${evidence.map((entry) => `<div class="evidence-card"><strong>${esc(humanize(entry?.kind || "evidence"))}</strong>${kvHtml(entry || {}, Object.keys(entry || {}))}</div>`).join("")}</div></div>`;
}
function explicitRoots(item) {
return [item.data || {}, item, ...(Array.isArray(item.evidence) ? item.evidence : [])];
}
function flatTextValues(value) {
if (value == null || value === "") return [];
if (Array.isArray(value)) return value.flatMap(flatTextValues);
if (typeof value === "object") {
const candidate = value.title || value.name || value.document || value.path || value.source_path || value.file || value.id || value.reason || "";
return candidate ? [String(candidate)] : [];
}
return [String(value)];
}
function collectExplicitValues(item, keys, limit = 4) {
const seen = new Set();
const values = [];
for (const root of explicitRoots(item)) {
if (!root || typeof root !== "object" || Array.isArray(root)) continue;
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(root, key)) continue;
for (const value of flatTextValues(root[key])) {
const normalized = value.trim();
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
values.push(normalized);
if (values.length >= limit) return values;
}
}
}
return values;
}
function requestedDocumentHints(item) {
return collectExplicitValues(item, [
"requested_document",
"requested_documents",
"missing_document",
"missing_documents",
"required_document",
"required_documents",
"support_document",
"support_documents",
"source_document",
"source_documents",
]);
}
function editTargetSummary(item) {
const data = item.data || {};
if (data.edit_hint) return String(data.edit_hint);
const artifact = data.target_artifact || item.output_path || "";
const field = data.target_field || "";
const recordId = data.target_record_id || "";
const idField = data.target_id_field || "";
const recordsKey = data.target_records_key || "";
if (!artifact) return uiText("revisionOnly", "Revision file only until workflow apply updates a native artifact");
const details = [];
if (field) details.push(`${uiText("targetField", "field")}: ${field}`);
if (recordId) details.push(`${idField || uiText("targetRecord", "record")}: ${recordId}`);
if (recordsKey) details.push(`${uiText("targetRecords", "records")}: ${recordsKey}`);
return `${artifact}${details.length ? " (" + details.join(", ") + ")" : ""}`;
}
function decisionImpactItem(label, value) {
return `<div class="decision-impact__item"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`;
}
function decisionImpactHtml(item, action) {
const canPersist = canPersistThroughBridge();
const rows = [
decisionImpactItem(uiText("currentAction", "Action"), action ? actionLabel(action) : uiText("noDecisionYet", "No decision selected")),
decisionImpactItem(uiText("writeMode", "Write mode"), canPersist ? uiText("mcpWriteMode", "MCP save/apply") : uiText("copyOnlyMode", "Copy JSON only")),
];
const targetSummary = editTargetSummary(item);
const hasExplicitTarget = Boolean(item.data?.target_artifact || item.data?.target_field || item.data?.edit_hint);
if (action === "edit" || hasExplicitTarget) {
rows.push(decisionImpactItem(uiText("writeTarget", "Write target"), targetSummary || uiText("notTargeted", "No direct artifact target")));
}
const docs = requestedDocumentHints(item);
if (action === "request_more_documents" || docs.length) {
rows.push(decisionImpactItem(uiText("documentHint", "Document hint"), docs.length ? docs.join(", ") : uiText("noneValue", "None")));
}
return `<div class="decision-impact"><div class="decision-impact__title">${esc(uiText("decisionImpact", "Decision impact"))}</div><div class="decision-impact__grid">${rows.join("")}</div></div>`;
}
function decisionControlsHtml(item) {
const decision = decisionFor(item.id);
const action = decision?.action || "";
const note = decision?.reviewer_note || "";
const editValue = decision?.edit_value || "";
const docs = Array.isArray(decision?.requested_documents) ? decision.requested_documents.join("\n") : "";
const buttons = (item.allowed_actions || []).map((candidate) => `<button class="decision-choice" type="button" data-decision-action="${esc(candidate)}" aria-pressed="${candidate === action}">${esc(actionLabel(candidate))}${candidate === item.recommended_action ? " *" : ""}</button>`).join("");
return `<section class="decision-box"><div class="decision-grid">${buttons}</div>${decisionImpactHtml(item, action)}<label class="field"><span class="field-label">${esc(ui("fields", "reviewerNote", "Reviewer note"))}</span><textarea data-decision-field="reviewer_note">${esc(note)}</textarea></label>${action === "edit" ? `<label class="field"><span class="field-label">${esc(ui("fields", "editValue", "Edit value"))}</span><textarea data-decision-field="edit_value">${esc(editValue)}</textarea></label>` : ""}${action === "request_more_documents" ? `<label class="field"><span class="field-label">${esc(ui("fields", "requestedDocuments", "Requested documents"))}</span><textarea data-decision-field="requested_documents">${esc(docs)}</textarea></label>` : ""}</section>`;
}
function detailsHtml(item) {
if (!item) return items().length ? `<div class="empty">${esc(uiText("selectItem", "Select a review item."))}</div>` : `<div class="empty-state"><strong>${esc(uiText("waitingTitle", "Waiting for review data"))}</strong><p>${esc(uiText("waitingBody", "Render this widget from the MCP tool to load review items, evidence, and outputs."))}</p></div>`;
const decision = decisionFor(item.id);
const fields = [[ui("fields", "type", "Type"), humanize(item.item_type)], [ui("fields", "recommended", "Recommended"), actionLabel(item.recommended_action || "")], [ui("fields", "decision", "Decision"), decision ? actionLabel(decision.action) : ""], [ui("fields", "source", "Source"), item.source_path || ""], [ui("fields", "output", "Output"), item.output_path || ""]].filter(([, value]) => value);
const status = statusFor(item);
return `<section class="review-heading"><div><h3>${esc(item.title)}</h3><p>${esc(supportLine(item) || uiText("readyDecision", "Ready for reviewer decision."))}</p></div><span class="status-token">${esc(statusLabel(status))}</span></section><dl>${fields.map(([key, value]) => `<dt>${esc(key)}</dt><dd>${esc(value)}</dd>`).join("")}</dl>${outputLinkHtml(item.source_path, true)}${outputLinkHtml(item.output_path)}${decisionControlsHtml(item)}${workflowDetailHtml(item)}${evidenceHtml(item)}`;
}
function renderDetails() { document.getElementById("details").innerHTML = detailsHtml(itemById(state.selectedId)); }
function artifactRecords() {
const finalOutputs = Array.isArray(state.payload.final_artifacts?.outputs) ? state.payload.final_artifacts.outputs : [];
if (finalOutputs.length) return finalOutputs;
return items().filter((item) => String(item.item_type || "").includes("artifact") || item.output_path).map((item) => ({ path: item.output_path || item.title, kind: String(item.output_path || "file").split(".").pop(), status: item.data?.exists === false ? "missing" : "written" })).slice(0, 8);
}
function artifactMetaText(record) {
const parts = [statusLabel(record.status || "written"), record.kind || "file"];
if (record.size_bytes != null && record.size_bytes !== "") {
parts.push(`${formatValue(record.size_bytes)} ${uiText("artifactBytes", "bytes")}`);
}
return parts.join(" · ");
}
function compactListValue(value) {
if (value == null || value === "") return "";
if (typeof value === "object") {
if (value.action || value.followup_context || value.requested_documents) {
return blockerSummary(value);
}
const candidate = value.title || value.reason || value.message || value.item_id || value.path || value.status || "";
return candidate ? String(candidate) : JSON.stringify(value);
}
return formatValue(value);
}
function contextSummary(context, limit = 4) {
if (!context || typeof context !== "object" || Array.isArray(context)) return "";
const entries = Object.entries(context)
.map(([key, value]) => {
const formatted = formatValue(value);
return formatted ? `${humanize(key)}: ${formatted}` : "";
})
.filter(Boolean);
if (!entries.length) return "";
const visible = entries.slice(0, limit).join("; ");
const overflow = entries.length - limit;
if (overflow <= 0) return visible;
return `${visible}; ${uiText("artifactMore", "+{count} more").replace("{count}", overflow)}`;
}
function blockerSummary(value) {
const head = value.title || value.item_id || statusLabel(value.status || value.action || "blocked");
const parts = [head];
const docs = listSummary(value.requested_documents, 2);
if (docs) parts.push(`${ui("fields", "requestedDocuments", "Requested documents")}: ${docs}`);
const context = contextSummary(value.followup_context, 4);
if (context) parts.push(context);
if (value.reviewer_note) parts.push(value.reviewer_note);
return parts.filter(Boolean).join(" | ");
}
function listSummary(value, limit = 4) {
const values = Array.isArray(value) ? value : [value];
const entries = values.map(compactListValue).filter(Boolean);
if (!entries.length) return "";
const visible = entries.slice(0, limit).join(", ");
const overflow = entries.length - limit;
if (overflow <= 0) return visible;
return `${visible}, ${uiText("artifactMore", "+{count} more").replace("{count}", overflow)}`;
}
function artifactTagHtml(label, value) {
const summary = listSummary(value);
if (!summary) return "";
return `<span class="artifact-tag"><strong>${esc(label)}</strong>${esc(summary)}</span>`;
}
function sheetHeaderSummary(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
return Object.entries(value).map(([sheet, headers]) => {
const headerText = listSummary(Array.isArray(headers) ? headers : [headers], 3);
return headerText ? `${sheet}: ${headerText}` : sheet;
});
}
function cellSummary(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
return Object.entries(value).map(([sheet, cells]) => {
if (!cells || typeof cells !== "object" || Array.isArray(cells)) return sheet;
const cellText = listSummary(Object.entries(cells).map(([cell, expected]) => `${cell}=${compactListValue(expected)}`), 3);
return cellText ? `${sheet}: ${cellText}` : sheet;
});
}
function artifactTagsHtml(record) {
const tags = [
record.row_count != null && record.row_count !== "" ? artifactTagHtml(uiText("artifactRows", "Rows"), record.row_count) : "",
record.min_rows != null && record.min_rows !== "" ? artifactTagHtml(uiText("artifactMinRows", "Min rows"), record.min_rows) : "",
artifactTagHtml(uiText("artifactSheets", "Sheets"), record.required_sheets),
artifactTagHtml(uiText("artifactHeaders", "Headers"), sheetHeaderSummary(record.required_sheet_headers)),
artifactTagHtml(uiText("artifactCells", "Cells"), cellSummary(record.required_cells)),
artifactTagHtml(uiText("artifactText", "Text"), record.required_text),
artifactTagHtml(uiText("artifactColumns", "Columns"), record.required_columns),
artifactTagHtml(uiText("artifactRecords", "Records"), record.records_key),
].filter(Boolean);
return tags.length ? `<div class="artifact-tags">${tags.join("")}</div>` : "";
}
function artifactDetailEntries(value) {
if (value == null || value === "") return [];
if (Array.isArray(value)) return value.map(compactListValue).filter(Boolean);
if (typeof value === "object") {
return Object.entries(value).map(([key, nested]) => {
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
const nestedSummary = listSummary(Object.entries(nested).map(([nestedKey, nestedValue]) => `${nestedKey}=${compactListValue(nestedValue)}`), 8);
return nestedSummary ? `${humanize(key)}: ${nestedSummary}` : humanize(key);
}
const nestedSummary = listSummary(nested, 8);
return nestedSummary ? `${humanize(key)}: ${nestedSummary}` : humanize(key);
}).filter(Boolean);
}
const single = compactListValue(value);
return single ? [single] : [];
}
function artifactQaSectionHtml(label, value) {
const entries = artifactDetailEntries(value);
if (!entries.length) return "";
const visible = entries.slice(0, 12);
const overflow = entries.length - visible.length;
const rows = visible.map((entry) => `<li>${esc(entry)}</li>`);
if (overflow > 0) rows.push(`<li>${esc(uiText("artifactMore", "+{count} more").replace("{count}", overflow))}</li>`);
return `<div class="artifact-qa-section"><strong>${esc(label)}</strong><ul>${rows.join("")}</ul></div>`;
}
function artifactQaHtml(record) {
const sections = [
artifactQaSectionHtml(uiText("artifactQaChecks", "QA checks"), record.qa_checks),
artifactQaSectionHtml(uiText("artifactRequiredText", "Required text"), record.required_text),
artifactQaSectionHtml(uiText("artifactRequiredColumns", "Required columns"), record.required_columns),
artifactQaSectionHtml(uiText("artifactRequiredSheets", "Required sheets"), record.required_sheets),
artifactQaSectionHtml(uiText("artifactRequiredHeaders", "Required headers"), record.required_sheet_headers),
artifactQaSectionHtml(uiText("artifactRequiredCells", "Required cells"), record.required_cells),
].filter(Boolean);
if (!sections.length) return "";
return `<details class="artifact-qa"><summary>${esc(uiText("artifactQaDetails", "Verification details"))}</summary><div class="artifact-qa-grid">${sections.join("")}</div></details>`;
}
function artifactNoteHtml(label, values) {
const summary = listSummary(values, 3);
if (!summary) return "";
return `<div class="artifact-note"><strong>${esc(label)}</strong><span>${esc(summary)}</span></div>`;
}
function artifactNotesHtml() {
const artifacts = state.payload.final_artifacts || {};
const notes = [
artifactNoteHtml(uiText("blockers", "Blockers"), artifacts.blockers),
artifactNoteHtml(uiText("caveats", "Caveats"), artifacts.caveats),
artifactNoteHtml(uiText("nextActions", "Next actions"), artifacts.next_actions),
].filter(Boolean);
return notes.length ? `<div class="artifact-notes">${notes.join("")}</div>` : "";
}
function outputLinkHtml(path, source = false) {
if (!path) return "";
const downloadable = !source && typeof window.localReviewDownloadOutput === "function";
const label = activeLanguage() === "it" ? (downloadable ? "Scarica documento" : "Copia percorso") : (downloadable ? "Download file" : "Copy path");
return `<button class="button" type="button" data-output-path="${esc(path)}" data-copy-only="${source}">${esc(label)}</button><span role="status" class="output-link-status"></span>`;
}
async function handleOutputLink(event) {
const button = event.target.closest("button[data-output-path]");
if (!button) return;
const message = button.nextElementSibling;
try {
if (button.dataset.copyOnly !== "true" && typeof window.localReviewDownloadOutput === "function") await window.localReviewDownloadOutput(button.dataset.outputPath);
else await navigator.clipboard.writeText(button.dataset.outputPath);
const downloaded = button.dataset.copyOnly !== "true" && typeof window.localReviewDownloadOutput === "function";
message.textContent = activeLanguage() === "it" ? (downloaded ? "Documento scaricato." : "Percorso copiato.") : (downloaded ? "File downloaded." : "Path copied.");
} catch (error) { message.textContent = error.message || String(error); }
}
function artifactRecordHtml(record) {
return `<div class="artifact-item"><strong>${esc(record.path || record.title || uiText("output", "Output"))}</strong><span>${esc(artifactMetaText(record))}</span>${artifactTagsHtml(record)}${artifactQaHtml(record)}${outputLinkHtml(record.path)}</div>`;
}
function renderArtifacts() {
const records = artifactRecords();
const node = document.getElementById("artifact-strip");
if (!records.length && !items().length) { node.innerHTML = ""; node.style.display = "none"; return; }
node.style.display = "";
node.innerHTML = `<div class="artifact-header"><h2>${esc(uiText("finalOutputs", "Final outputs"))}</h2><span>${records.length} ${esc(records.length === 1 ? uiText("fileSingular", "file") : uiText("filePlural", "files"))}</span></div>${artifactNotesHtml()}<div class="artifact-grid">${records.length ? records.map(artifactRecordHtml).join("") : `<div class="empty">${esc(uiText("outputsPending", "Outputs appear after the run writes final artifacts."))}</div>`}</div>`;
}
function ensureDecision(item, action = null) {
if (!item) return null;
const current = state.decisions[item.id] || {};
const nextAction = action || current.action || item.recommended_action || item.allowed_actions?.[0] || "mark_unclear";
const requestedDocuments = Array.isArray(current.requested_documents) && current.requested_documents.length
? current.requested_documents
: (nextAction === "request_more_documents" ? requestedDocumentHints(item) : []);
state.decisions[item.id] = { item_id: item.id, action: nextAction, reviewer_note: current.reviewer_note || "", edit_value: current.edit_value ?? (CONFIG.plugin === "report-builder" && nextAction === "edit" ? String(item.data?.[item.data?.target_field] ?? "") : ""), requested_documents: requestedDocuments };
return state.decisions[item.id];
}
function setDecisionAction(item, action) { if (!item || !action) return; ensureDecision(item, action); persistWidgetState(); renderRows(); }
function setDecisionField(item, field, value) {
const decision = ensureDecision(item);
if (!decision) return;
if (field === "requested_documents") decision.requested_documents = String(value || "").split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
else decision[field] = String(value || "");
persistWidgetState();
renderRows();
}
function collectDecisionInputs() {
return Object.values(state.decisions).filter((decision) => decision?.item_id && decision?.action && itemById(decision.item_id)).map((decision) => {
const payload = { item_id: decision.item_id, action: decision.action };
if (decision.reviewer_note) payload.reviewer_note = decision.reviewer_note.trim();
if (decision.edit_value) payload.edit_value = decision.edit_value.trim();
if (Array.isArray(decision.requested_documents) && decision.requested_documents.length) payload.requested_documents = decision.requested_documents;
return payload;
});
}
function validateDecisionInputs(decisions) {
for (const decision of decisions) {
if (decision.action === "edit" && !decision.edit_value) throw new Error(uiText("editRequired", "Edit value required for {item}.").replace("{item}", itemById(decision.item_id)?.title || decision.item_id));
}
}
function fallbackUiDecisions() {
const payload = reviewPayload();
const now = new Date().toISOString();
const decisions = collectDecisionInputs().map((decision) => {
const item = itemById(decision.item_id);
return { item_id: decision.item_id, item_type: item?.item_type || "", title: item?.title || "", action: decision.action, status: ACTION_STATUSES[decision.action] || "reviewed", decided_at: now, ...(decision.reviewer_note ? { reviewer_note: decision.reviewer_note } : {}), ...(decision.edit_value ? { edit_value: decision.edit_value } : {}), ...(decision.requested_documents?.length ? { requested_documents: decision.requested_documents } : {}) };
});
return { schema_version: payload.schema_version || "1.0", plugin: payload.plugin || CONFIG.plugin, workflow: payload.workflow || CONFIG.plugin, run_id: payload.run_id || "preview", decided_at: decisions.length ? now : null, decision_source: "mcp_widget_export", review_payload_path: state.payload.ui_decisions?.review_payload_path || "review_payload.json", decisions, decision_count: decisions.length, item_count: items().length, status: decisions.length === 0 ? "pending_review" : decisions.length === items().length ? "reviewed" : "partial_review" };
}
function retainedPredecessorCheckpoint() {
const hasPriorApplication = Boolean(
state.payload.final_artifacts?.review_application,
);
if (!hasPriorApplication) return null;
let checkpoint = String(
state.expectedPredecessorCheckpoint ||
state.payload.expected_predecessor_checkpoint ||
"",
).trim();
if (!/^[0-9a-f]{64}$/.test(checkpoint)) {
checkpoint = String(window.prompt(
"Enter the predecessor SHA-256 checkpoint retained through the separate review channel.",
"",
) || "").trim();
}
if (!/^[0-9a-f]{64}$/.test(checkpoint)) {
throw new Error("A valid external predecessor checkpoint is required for this later review round.");
}
state.expectedPredecessorCheckpoint = checkpoint;
return checkpoint;
}
function saveToolArgs() {
const expectedPredecessorCheckpoint = retainedPredecessorCheckpoint();
return {
run_intake: state.payload.run_intake || null,
review_payload: reviewPayload(),
ui_decisions: state.payload.ui_decisions || null,
decisions: collectDecisionInputs(),
decision_source: "mcp_widget",
...(expectedPredecessorCheckpoint ? {
expected_predecessor_checkpoint: expectedPredecessorCheckpoint,
} : {}),
};
}
function applyToolArgs() {
const expectedPredecessorCheckpoint = retainedPredecessorCheckpoint();
return {
run_intake: state.payload.run_intake || null,
review_payload: reviewPayload(),
ui_decisions: state.payload.ui_decisions || null,
final_artifacts: state.payload.final_artifacts || null,
decisions: collectDecisionInputs(),
decision_source: "mcp_widget",
...(expectedPredecessorCheckpoint ? {
expected_predecessor_checkpoint: expectedPredecessorCheckpoint,
} : {}),
};
}
function parseToolResult(result) {
const privatePayload = pickPayload(result);
if (privatePayload) return privatePayload;
if (result?.structuredContent) return result.structuredContent;
if (result?.ui_decisions || result?.ok != null) return result;
const text = Array.isArray(result?.content) ? result.content.find((entry) => entry?.text)?.text : null;
if (text) {
try { return JSON.parse(text); } catch { return { ok: false, error: text }; }
}
return result || {};
}
async function copyText(text) {
if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return; }
const textarea = document.createElement("textarea");
textarea.value = text; textarea.setAttribute("readonly", ""); textarea.style.position = "fixed"; textarea.style.opacity = "0";
document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); textarea.remove();
}
async function copyDecisionJson() { await copyText(`${JSON.stringify(fallbackUiDecisions(), null, 2)}\n`); }
function setSaveStatus(message, kind = "") {
const node = document.getElementById("save-status");
node.textContent = message || "";
node.className = `status-message${kind ? ` is-${kind}` : ""}`;
}
async function saveCurrentDecisions() {
const saveTool = state.payload.decision_policy?.save_tool || CONFIG.saveTool;
if (typeof window.openai?.callTool !== "function") {
await copyDecisionJson();
setRecoveryIssue("fallback", uiText("fallbackActive", "JSON fallback active"), uiText("fallbackBody", "Copy or download ui_decisions.json. Use the MCP or local-server review surface to persist and apply decisions."), saveTool);
return { ok: true, copied: true, message: uiText("toolCopied", "Tool bridge unavailable. JSON copied.") };
}
const result = parseToolResult(await window.openai.callTool(saveTool, saveToolArgs()));
if (!result?.ok) throw new Error(result?.error || uiText("saveFailed", "Decision save failed."));
if (result.ui_decisions) state.payload.ui_decisions = result.ui_decisions;
return result;
}
async function handleSaveDecisions() {
const decisions = collectDecisionInputs();
try { validateDecisionInputs(decisions); } catch (error) { setSaveStatus(error.message || String(error), "error"); return; }
setSaveStatus(uiText("saving", "Saving..."));
try {
const result = await saveCurrentDecisions();
if (!result.copied) clearRecoveryIssue();
setSaveStatus(result.message || uiText("decisionsSaved", "Decisions saved."), "ok");
renderProgress();
} catch (error) {
setRecoveryIssue("error", uiText("saveFailedTitle", "Save did not persist"), uiText("retryWithMcp", "Retry from the MCP or local-server review surface, or copy/download decision JSON for handoff."), state.payload.decision_policy?.save_tool || CONFIG.saveTool, error.message || String(error));
setSaveStatus(error.message || String(error), "error");
}
}
async function handleApplyDecisions() {
const decisions = collectDecisionInputs();
try { validateDecisionInputs(decisions); } catch (error) { setSaveStatus(error.message || String(error), "error"); return; }
if (typeof window.openai?.callTool !== "function") {
try {
await copyDecisionJson();
setRecoveryIssue("fallback", uiText("applyFailedTitle", "Apply did not persist"), uiText("applyRequiresMcp", "Apply requires the MCP or local-server review surface."), state.payload.decision_policy?.apply_tool || CONFIG.applyTool);
setSaveStatus(uiText("toolCopiedApply", "Tool bridge unavailable. JSON copied; apply requires MCP."), "ok");
} catch (error) {
setRecoveryIssue("error", uiText("applyFailedTitle", "Apply did not persist"), uiText("retryWithMcp", "Retry from the MCP or local-server review surface, or copy/download decision JSON for handoff."), state.payload.decision_policy?.apply_tool || CONFIG.applyTool, error.message || String(error));
setSaveStatus(error.message || String(error), "error");
}
return;
}
setSaveStatus(uiText("savingApplying", "Saving and applying..."));
try {
await saveCurrentDecisions();
const applyTool = state.payload.decision_policy?.apply_tool || CONFIG.applyTool;
const result = parseToolResult(await window.openai.callTool(applyTool, applyToolArgs()));
if (!result?.ok) throw new Error(result?.error || uiText("applyFailed", "Decision apply failed."));
if (result.final_artifacts) state.payload.final_artifacts = result.final_artifacts;
if (result.applied_decisions) state.payload.applied_decisions = result.applied_decisions;
clearRecoveryIssue();
setSaveStatus(result.message || uiText("decisionsApplied", "Decisions applied."), "ok");
render();
} catch (error) {
setRecoveryIssue("error", uiText("applyFailedTitle", "Apply did not persist"), uiText("retryWithMcp", "Retry from the MCP or local-server review surface, or copy/download decision JSON for handoff."), state.payload.decision_policy?.apply_tool || CONFIG.applyTool, error.message || String(error));
setSaveStatus(error.message || String(error), "error");
}
}
function downloadDecisionJson() {
const blob = new Blob([`${JSON.stringify(fallbackUiDecisions(), null, 2)}\n`], { type: "application/json" });
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url; anchor.download = "ui_decisions.json"; document.body.appendChild(anchor); anchor.click(); anchor.remove(); URL.revokeObjectURL(url);
setSaveStatus(uiText("jsonDownloaded", "JSON downloaded."), "ok");
}
function applyRecommendedToVisible() { for (const item of filteredItems()) ensureDecision(item, item.recommended_action || item.allowed_actions?.[0] || "mark_unclear"); persistWidgetState(); renderRows(); }
function persistWidgetState() {
try { window.openai?.setWidgetState?.({ run_id: reviewPayload().run_id || null, decisions: state.decisions }); } catch { }
}
function loadInitialDecisions() {
const current = {};
let savedDecisionCount = 0;
for (const decision of (Array.isArray(state.payload.ui_decisions?.decisions) ? state.payload.ui_decisions.decisions : [])) {
if (decision?.item_id && decision?.action && itemById(decision.item_id)) {
current[decision.item_id] = { item_id: decision.item_id, action: decision.action, reviewer_note: decision.reviewer_note || "", edit_value: decision.edit_value || "", requested_documents: Array.isArray(decision.requested_documents) ? decision.requested_documents : [] };
savedDecisionCount += 1;
}
}
const widgetState = window.openai?.widgetState;
let draftDecisionCount = 0;
if (widgetState?.run_id === reviewPayload().run_id && widgetState.decisions && typeof widgetState.decisions === "object") {
for (const [itemId, decision] of Object.entries(widgetState.decisions)) if (itemById(itemId) && decision?.action) {
current[itemId] = decision;
draftDecisionCount += 1;
}
}
state.decisions = current;
state.recovery = { savedDecisionCount, draftDecisionCount };
}
function setButtonText(id, text) {
const button = document.getElementById(id);
if (button) button.textContent = text;
}
function renderChrome() {
const lang = activeLanguage();
const title = workflowText("title", CONFIG.title);
document.documentElement.lang = lang;
document.title = title;
document.getElementById("app-title").textContent = title;
document.getElementById("status-pill").textContent = uiText("mcpWidget", "MCP widget");
setButtonText("use-recommended", ui("buttons", "useRecommended", "Use recommended"));
setButtonText("save-decisions", ui("buttons", "saveDecisions", "Save decisions"));
setButtonText("apply-decisions", ui("buttons", "applyDecisions", "Apply decisions"));
setButtonText("copy-decisions", ui("buttons", "copyJson", "Copy JSON"));
setButtonText("download-decisions", ui("buttons", "downloadJson", "Download JSON"));
document.getElementById("search").placeholder = workflowText("search", "Search section, table, source, blocker, artifact");
document.getElementById("queue-title").textContent = workflowText("queueTitle", uiText("reviewQueue", "Review queue"));
document.getElementById("detail-title").textContent = workflowText("detailTitle", uiText("evidenceDecision", "Evidence and decision"));
}
function loadDemoPayload() { state.payload = DEMO_PAYLOAD; state.selectedType = "all"; state.selectedState = "all"; state.selectedId = null; state.query = ""; document.getElementById("search").value = ""; loadInitialDecisions(); setSaveStatus(uiText("sampleLoaded", "Sample review loaded."), "ok"); render(); }
function render() {
renderChrome();
renderSummary(); renderContext(); renderDataPosture(); renderExecutionProvenance(); renderReviewSafeguards(); renderTabs(); renderRows(); renderProgress(); renderArtifacts();
}
document.getElementById("artifact-strip").addEventListener("click", handleOutputLink);
document.getElementById("details").addEventListener("click", handleOutputLink);
document.getElementById("search").addEventListener("input", (event) => { state.query = event.target.value; renderRows(); });
document.getElementById("tabs").addEventListener("click", (event) => { const button = event.target.closest("button[data-type]"); if (!button) return; state.selectedType = button.dataset.type; state.selectedId = null; render(); });
document.getElementById("state-strip").addEventListener("click", (event) => { const button = event.target.closest("button[data-state-filter]"); if (!button) return; state.selectedState = button.dataset.stateFilter; state.selectedId = null; render(); });
document.getElementById("rows").addEventListener("click", (event) => {
const demoButton = event.target.closest("button[data-load-demo]"); if (demoButton) { loadDemoPayload(); return; }
const emptyButton = event.target.closest("#copy-empty-contract"); if (emptyButton) { copyDecisionJson().then(() => setSaveStatus(uiText("emptyJsonCopied", "Empty decision JSON copied."), "ok")).catch((error) => setSaveStatus(error.message || String(error), "error")); return; }
const button = event.target.closest("button[data-id]"); if (!button) return; state.selectedId = button.dataset.id; renderRows();
});
document.getElementById("details").addEventListener("click", (event) => { const button = event.target.closest("button[data-decision-action]"); if (!button) return; setDecisionAction(itemById(state.selectedId), button.dataset.decisionAction); });
document.getElementById("details").addEventListener("input", (event) => { const field = event.target.closest("[data-decision-field]"); if (!field) return; setDecisionField(itemById(state.selectedId), field.dataset.decisionField, field.value); });
document.getElementById("use-recommended").addEventListener("click", applyRecommendedToVisible);
document.getElementById("save-decisions").addEventListener("click", handleSaveDecisions);
document.getElementById("apply-decisions").addEventListener("click", handleApplyDecisions);
document.getElementById("copy-decisions").addEventListener("click", () => copyDecisionJson().then(() => setSaveStatus(uiText("jsonCopied", "JSON copied."), "ok")).catch((error) => setSaveStatus(error.message || String(error), "error")));
document.getElementById("download-decisions").addEventListener("click", downloadDecisionJson);
window.addEventListener("message", (event) => {
if (event?.data?.method !== "ui/notifications/tool-result") return;
const nextPayload = pickPayload(event.data.params);
if (!nextPayload || (nextPayload.widget_type !== CONFIG.widgetType && !nextPayload.review_payload)) return;
state.payload = nextPayload;
loadInitialDecisions();
render();
});
state.payload = toolOutput();
loadInitialDecisions();
render();
</script>
</body>
</html>
SHA-256: b1b0fc0a1320296ec669cca1ddb5df33bf7c5cb1947ee1f24b90516c8b9266e9