← Files VeraARCHIVED FILE
modules/client-file-preparation/assets/client-file-preparation-review-widget.html
156 KB · Oct 3, 2026 · 06:30 UTC
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>New Client · File Preparation</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-actions > .progress-meter { grid-column: 1; grid-row: 1; }
.review-actions > .action-buttons { grid-column: 2; grid-row: 1; }
.reviewer-alias-field {
grid-column: 1 / -1;
grid-row: 2;
display: grid;
grid-template-columns: minmax(9rem, 0.28fr) minmax(12rem, 1fr);
gap: 0.28rem 0.75rem;
align-items: center;
border-top: 1px solid var(--border);
padding-top: 0.72rem;
}
.reviewer-alias-field input {
width: min(100%, 30rem);
min-height: 2.45rem;
border: 1px solid var(--border-strong);
border-radius: var(--radius);
padding: 0.48rem 0.65rem;
background: var(--surface);
color: var(--ink);
outline: none;
}
.reviewer-alias-field input:focus { border-color: var(--accent); }
.reviewer-alias-help {
grid-column: 2;
max-width: 72ch;
color: var(--muted);
font-size: 0.75rem;
line-height: 1.35;
}
@media (max-width: 980px) {
.review-actions > .progress-meter,
.review-actions > .action-buttons,
.reviewer-alias-field { grid-column: 1; grid-row: auto; }
.reviewer-alias-field { grid-template-columns: 1fr; }
.reviewer-alias-help { grid-column: 1; }
}
.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; }
}
.workflow-card--draft { grid-column: 1 / -1; }
.workflow-card--draft .kv > div:nth-child(even) { white-space: pre-wrap; }
</style>
</head>
<body>
<main class="app">
<div class="shell">
<section class="topbar">
<div>
<h1 id="app-title">New Client · File Preparation</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 document, missing item, memo, email, blocker">
<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>
<label class="reviewer-alias-field" for="reviewer-alias">
<span class="field-label" id="reviewer-alias-label">Reviewer</span>
<input id="reviewer-alias" type="text" maxlength="160" autocomplete="off" spellcheck="false" placeholder="Maria Rossi">
<small class="reviewer-alias-help" id="reviewer-alias-help">Use a stable professional or account reference. A real professional name is allowed. Do not enter credentials, session material, or raw local paths.</small>
</label>
<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":"client-file-preparation","title":"New Client \u00b7 File Preparation","reviewTitle":"File Preparation Review","queueTitle":"Documents to Review","detailTitle":"Document Decision","detailMode":"intake-board","detailHelp":"Classify documents, confirm missing items, and capture the next client request.","detailGroups":[{"title":"Client Document","variant":"document","fields":["document","client","fiscal_year","status","source_path"],"empty":"No document fields."},{"title":"Required Evidence","variant":"checklist","fields":["request_text","requested_document","reason","reasons","label","value","status","confidence"],"empty":"No required-evidence fields."},{"title":"Memo / Email Impact","variant":"draft","fields":["preview","memo_section","email_text","review_notes","reason"],"empty":"No draft impact."}],"panels":["Client document","Required evidence","Preparation decision"],"localized":{"es":{"title":"Nuevo cliente \u00b7 Preparaci\u00f3n de documentos","reviewTitle":"Revisi\u00f3n de la preparaci\u00f3n de documentos","queueTitle":"Documentos para revisar","detailTitle":"Decisi\u00f3n sobre el documento","detailHelp":"Clasifique los documentos, confirme los faltantes y registre la siguiente solicitud al cliente.","search":"Buscar documento, faltante, informe, correo, bloqueo","reviewerAlias":"Revisor","reviewerAliasHelp":"Use una referencia estable del profesional o de su cuenta. Se admite el nombre real del profesional. No introduzca credenciales, datos de sesi\u00f3n ni rutas locales sin depurar.","reviewerAliasInvalid":"La referencia del revisor puede tener un m\u00e1ximo de 160 caracteres y no debe contener credenciales, datos de sesi\u00f3n ni rutas locales sin depurar."},"it":{"title":"Nuovo cliente \u00b7 Preparazione documenti","reviewTitle":"Revisione preparazione documenti","queueTitle":"Documenti da rivedere","detailTitle":"Decisione documento","detailHelp":"Classifica documenti, conferma mancanti e cattura la prossima richiesta cliente.","search":"Cerca documento, mancante, memo, email, blocco","reviewerAlias":"Revisore","reviewerAliasHelp":"Usa un riferimento stabile del professionista o del suo account. \u00c8 ammesso il nome reale del professionista. Non inserire credenziali, materiale di sessione o percorsi locali grezzi.","reviewerAliasInvalid":"Il riferimento del revisore pu\u00f2 avere al massimo 160 caratteri e non deve contenere credenziali, materiale di sessione o percorsi locali grezzi."},"fr":{"title":"Nouveau client \u00b7 Preparation des documents","reviewTitle":"Revue de la preparation des documents","queueTitle":"Documents a revoir","detailTitle":"Decision document","detailHelp":"Classez les documents, confirmez les manquants et saisissez la prochaine demande client.","search":"Chercher document, manquant, memo, email, blocage","reviewerAlias":"R\u00e9viseur","reviewerAliasHelp":"Utilisez une r\u00e9f\u00e9rence stable du professionnel ou de son compte. Le nom r\u00e9el du professionnel est admis. Ne saisissez ni identifiants de connexion, ni donn\u00e9es de session, ni chemins locaux bruts.","reviewerAliasInvalid":"La r\u00e9f\u00e9rence du r\u00e9viseur est limit\u00e9e \u00e0 160 caract\u00e8res et ne doit contenir ni identifiants de connexion, ni donn\u00e9es de session, ni chemins locaux bruts."},"de":{"title":"Neuer Mandant \u00b7 Unterlagen vorbereiten","reviewTitle":"Unterlagenvorbereitung pruefen","queueTitle":"Zu pruefende Dokumente","detailTitle":"Dokumententscheidung","detailHelp":"Dokumente klassifizieren, fehlende Punkte bestaetigen und naechste Mandantenanfrage erfassen.","search":"Dokument, fehlender Punkt, Memo, E-Mail, Blocker suchen","reviewerAlias":"Pr\u00fcfer","reviewerAliasHelp":"Verwenden Sie eine stabile Referenz des Berufstr\u00e4gers oder seines Kontos. Der echte Name des Berufstr\u00e4gers ist zul\u00e4ssig. Keine Zugangsdaten, Sitzungsdaten oder unbereinigten lokalen Pfade eingeben.","reviewerAliasInvalid":"Die Pr\u00fcferreferenz darf h\u00f6chstens 160 Zeichen lang sein und keine Zugangsdaten, Sitzungsdaten oder unbereinigten lokalen Pfade enthalten."}},"saveTool":"save_client_file_preparation_decisions","applyTool":"apply_client_file_preparation_decisions","widgetType":"client_file_preparation_review","demo":{"review_type":"client_file_preparation_review","items":[{"id":"doc-1","item_type":"document_inventory","title":"CU 2025 - Example Client","source_path":"cliente/CU_2025.pdf","output_path":"document_inventory.json","allowed_actions":["accept","edit","mark_unclear","skip"],"recommended_action":"accept","data":{"document":"CU 2025","client":"Example Client","status":"classified","fiscal_year":"2025","target_artifact":"document_inventory.json","target_id_field":"document","target_record_id":"CU 2025","target_field":"review_notes","edit_hint":"Editing this document row updates review_notes in document_inventory.json."},"evidence":[{"kind":"document_classification","confidence":"high"}]},{"id":"missing-1","item_type":"missing_document_request","title":"Missing: F24 payment receipt","source_path":"cliente/","output_path":"missing_documents.json","allowed_actions":["accept","reject","edit","request_more_documents"],"recommended_action":"request_more_documents","data":{"document":"F24 payment receipt","status":"missing","reason":"Required to reconcile tax payments.","requested_document":"F24 payment receipt","target_artifact":"missing_documents.json","target_id_field":"document","target_record_id":"F24 payment receipt","target_field":"review_notes","edit_hint":"Editing this missing-document row updates review_notes in missing_documents.json."},"evidence":[{"kind":"missing_document_queue","requested_document":"F24"}]}]},"requiresReviewerAlias":true};
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","Output Trace":"Traccia output","Bank Statement":"Estratto conto","Match Result":"Esito match","Draft Section":"Sezione bozza","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: {}, reviewerAlias: "", 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"};
function humanize(value) {
const key = String(value || "");
const labels = {"it": {"request_text": "Richiesta proposta", "preview": "Anteprima della bozza", "label": "Campo", "value": "Valore", "reasons": "Motivi", "document_inventory": "Documenti ricevuti", "uncertain_file": "Documento da chiarire", "missing_document_request": "Richieste al cliente", "extracted_fiscal_field": "Dati fiscali", "draft_memo_section": "Memo dello studio", "draft_client_email": "Bozza email"}, "en": {"request_text": "Proposed request", "preview": "Draft preview", "label": "Field", "value": "Value", "reasons": "Reasons", "document_inventory": "Received documents", "uncertain_file": "Uncertain document", "missing_document_request": "Client requests", "extracted_fiscal_field": "Fiscal data", "draft_memo_section": "Studio memo", "draft_client_email": "Email draft"}, "fr": {"request_text": "Demande propos\u00e9e", "preview": "Aper\u00e7u du brouillon", "label": "Champ", "value": "Valeur", "reasons": "Motifs", "document_inventory": "Documents re\u00e7us", "uncertain_file": "Document \u00e0 clarifier", "missing_document_request": "Demandes au client", "extracted_fiscal_field": "Donn\u00e9es fiscales", "draft_memo_section": "Note du cabinet", "draft_client_email": "Brouillon du courriel"}, "de": {"request_text": "Vorgeschlagene Anfrage", "preview": "Entwurfsvorschau", "label": "Feld", "value": "Wert", "reasons": "Gr\u00fcnde", "document_inventory": "Erhaltene Dokumente", "uncertain_file": "Zu kl\u00e4rendes Dokument", "missing_document_request": "Mandantenanfragen", "extracted_fiscal_field": "Steuerdaten", "draft_memo_section": "Kanzleinotiz", "draft_client_email": "E-Mail-Entwurf"}, "es": {"request_text": "Solicitud propuesta", "preview": "Vista previa del borrador", "label": "Campo", "value": "Valor", "reasons": "Motivos", "document_inventory": "Documentos recibidos", "uncertain_file": "Documento por aclarar", "missing_document_request": "Solicitudes al cliente", "extracted_fiscal_field": "Datos fiscales", "draft_memo_section": "Nota del despacho", "draft_client_email": "Borrador del correo"}};
if (labels[activeLanguage()]?.[key]) return labels[activeLanguage()][key];
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 });
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 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>${esc(formatValue(value))}</div>`).join("")}</div>`;
}
function valueForField(item, field) {
if (field === "preview" && ["draft_client_email", "draft_memo_section"].includes(item.item_type)) {
const effects = state.payload.applied_decisions?.effects || [];
const applied = effects.find(effect => effect.item_id === item.id && effect.applied === true
&& effect.artifact_update === "target_artifact_updated" && effect.target_artifact === item.output_path);
if (typeof applied?.edit_value === "string") return applied.edit_value;
}
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 (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 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>${esc(formatValue(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)}${workflowDetailHtml(item)}${decisionControlsHtml(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>`;
}
const MAX_REVIEWER_REFERENCE_LENGTH = 160;
const REVIEWER_REFERENCE_SECRET_RE = /(?:password|passwd|api[_ -]?key|access[_ -]?token|refresh[_ -]?token|session[_ -]?(?:token|cookie)|authorization)\s*[:=]/i;
const REVIEWER_REFERENCE_CREDENTIAL_VALUE_RE = /^(?:sk-[A-Za-z0-9_-]{16,}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.)/;
function reviewerAliasValue() { return String(state.reviewerAlias || "").trim(); }
function validateReviewerReference(decisions) {
if (!decisions.length) return;
const reference = reviewerAliasValue();
const isUnsafe = reference.length > MAX_REVIEWER_REFERENCE_LENGTH
|| /[\u0000-\u001f\u007f]/.test(reference)
|| reference.includes("/")
|| reference.includes("\\")
|| REVIEWER_REFERENCE_SECRET_RE.test(reference)
|| REVIEWER_REFERENCE_CREDENTIAL_VALUE_RE.test(reference);
if (reference && isUnsafe) throw new Error(workflowText("reviewerAliasInvalid", "Reviewer reference must be at most 160 characters and must not contain credentials, session material, or raw local paths."));
}
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 || "", 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) {
validateReviewerReference(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", ...(reviewerAliasValue() ? { reviewer: reviewerAliasValue() } : {}), decisions, decision_count: decisions.length, item_count: items().length, status: decisions.length === 0 ? "pending_review" : decisions.length === items().length ? "reviewed" : "partial_review" };
}
function saveToolArgs() {
const token = state.payload.decision_policy?.persistence_token || null;
return {
persistence_token: token,
...(token ? {} : {
run_intake: state.payload.run_intake || null,
review_payload: reviewPayload(),
ui_decisions: state.payload.ui_decisions || null,
}),
decisions: collectDecisionInputs(),
decision_source: "mcp_widget",
reviewer: reviewerAliasValue() || null,
};
}
function applyToolArgs() {
const token = state.payload.decision_policy?.persistence_token || null;
return {
persistence_token: token,
...(token ? {} : {
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",
reviewer: reviewerAliasValue() || null,
};
}
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, reviewer_alias: reviewerAliasValue() || 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;
state.reviewerAlias = String(
state.payload.ui_decisions?.reviewer
|| (widgetState?.run_id === reviewPayload().run_id ? widgetState.reviewer_alias : "")
|| ""
).trim();
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"));
const reviewerAliasInput = document.getElementById("reviewer-alias");
if (reviewerAliasInput && reviewerAliasInput.value !== state.reviewerAlias) reviewerAliasInput.value = state.reviewerAlias;
const reviewerAliasLabel = document.getElementById("reviewer-alias-label");
if (reviewerAliasLabel) reviewerAliasLabel.textContent = workflowText("reviewerAlias", "Reviewer");
const reviewerAliasHelp = document.getElementById("reviewer-alias-help");
if (reviewerAliasHelp) reviewerAliasHelp.textContent = workflowText("reviewerAliasHelp", "Use a stable professional or account reference. A real professional name is allowed. Do not enter credentials, session material, or raw local paths.");
document.getElementById("search").placeholder = workflowText("search", "Search document, missing item, memo, email, blocker");
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("reviewer-alias")?.addEventListener("input", (event) => {
state.reviewerAlias = String(event.target.value || "").trim();
persistWidgetState();
});
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: 5eb2822023458679cb31ae5e0dfdc07ccf804d07719913fb5b7c234842979235