Expose Ranker source traces in result cards

This commit is contained in:
OpenClaw Bot
2026-05-27 15:30:34 +02:00
parent 470965b8b7
commit 5cd5dd2fcf
3 changed files with 35 additions and 7 deletions
+31 -3
View File
@@ -61,8 +61,13 @@ function parsePastedJsonPayload(value) {
try {
const parsed = JSON.parse(jsonText);
const looksLikeBridgePayload = parsed && typeof parsed === 'object' && !Array.isArray(parsed) && (
parsed.schema || parsed.featureSet || parsed.snapshot || parsed.conceptMap || parsed.buildOrder || parsed.lenses || parsed.reference_code || parsed.referenceCode || parsed.artifactId || parsed.ideaText
|| Array.isArray(parsed.nextActions) || Array.isArray(parsed.nextMoves) || Array.isArray(parsed.validateNext) || Array.isArray(parsed.deferred) || Array.isArray(parsed.parkingLot)
parsed.schema || parsed.featureSet || parsed.feature_set || parsed.snapshot || parsed.conceptMap || parsed.concept_map || parsed.buildOrder || parsed.build_order || parsed.lenses
|| parsed.payload || parsed.rankPayload || parsed.scattermindPayload || parsed.conceptMapJson
|| parsed.reference_code || parsed.referenceCode || parsed.artifactId || parsed.sourceArtifactId || parsed.source_artifact_id
|| parsed.ideaText || parsed.idea_text || parsed.originalPrompt || parsed.original_prompt || parsed.sourceSummary || parsed.source_summary || parsed.opening_reflection || parsed.restated_idea
|| Array.isArray(parsed.features) || Array.isArray(parsed.actions) || Array.isArray(parsed.candidates)
|| Array.isArray(parsed.nextActions) || Array.isArray(parsed.next_actions) || Array.isArray(parsed.nextMoves) || Array.isArray(parsed.next_moves)
|| Array.isArray(parsed.validateNext) || Array.isArray(parsed.validate_next) || Array.isArray(parsed.deferred) || Array.isArray(parsed.parkingLot) || Array.isArray(parsed.parking_lot)
);
return looksLikeBridgePayload ? parsed : null;
} catch {
@@ -105,7 +110,8 @@ function payloadFromForm(formPayload) {
const optionsJson = parsePastedJsonPayload(formPayload.optionsText);
const embedded = ideaJson || optionsJson;
if (!embedded) return formPayload;
const merged = { ...embedded };
const unwrapped = embedded.payload || embedded.rankPayload || embedded.scattermindPayload || embedded.conceptMapJson || embedded;
const merged = { ...unwrapped };
if (!merged.mode && formPayload.mode) merged.mode = formPayload.mode;
if (String(formPayload.context || '').trim() && !merged.context) merged.context = formPayload.context;
return merged;
@@ -142,6 +148,26 @@ function renderSourceTrace(sourceTrace = {}) {
`;
}
function renderItemSourceTrace(item = {}) {
const provenance = item.provenance || {};
const trace = {
sourceSection: provenance.sourceSection || '',
sourceId: provenance.sourceId || '',
sourceTitle: provenance.sourceTitle || '',
sourceQuote: provenance.sourceQuote || '',
};
const hasTrace = trace.sourceSection || trace.sourceId || trace.sourceQuote;
if (!hasTrace) return '';
const label = [trace.sourceTitle, trace.sourceId || trace.sourceSection].filter(Boolean).join(' · ');
return `
<details class="item-source-trace">
<summary>Source trace${label ? ` · ${escapeHtml(label)}` : ''}</summary>
${trace.sourceQuote ? `<p>${escapeHtml(trace.sourceQuote)}</p>` : ''}
${trace.sourceSection ? `<small>Section: ${escapeHtml(trace.sourceSection)}</small>` : ''}
</details>
`;
}
function renderHandoffStatus(handoff = {}) {
const readiness = handoff.readiness || {};
if (!readiness.status) return '';
@@ -174,6 +200,7 @@ function renderRankCard(item) {
<div><span>Evidence question</span><p>${escapeHtml(item.evidenceQuestion || 'What proof would change this ranking?')}</p></div>
<div><span>Success / kill signal</span><p>${escapeHtml(item.successSignal || '')} ${escapeHtml(item.killSignal ? `Kill if: ${item.killSignal}` : '')}</p></div>
</div>
${renderItemSourceTrace(item)}
${renderMetrics(item.metrics)}
${renderPills(item.scoringNotes || [])}
</article>
@@ -210,6 +237,7 @@ function sourceCitation(data) {
trace.sourceTitle ? `Source title: ${trace.sourceTitle}` : '',
trace.sourceQuote ? `Source quote: ${trace.sourceQuote}` : '',
source.originalPromptExcerpt ? `Original prompt: ${source.originalPromptExcerpt}` : '',
source.sourceSummaryExcerpt ? `Source summary: ${source.sourceSummaryExcerpt}` : '',
].filter(Boolean);
return parts.length ? parts.join('\n') : 'No source citation was carried into this result.';
}
+3 -3
View File
@@ -4,9 +4,9 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#f3eee4" />
<meta name="rank-version" content="2.2.0-feedback-front-door" />
<meta name="rank-version" content="2.2.1-source-trace-paste" />
<title>Ranker — feedback front door for messy decisions</title>
<link rel="stylesheet" href="/styles.css?v=2.2.0-feedback-front-door" />
<link rel="stylesheet" href="/styles.css?v=2.2.1-source-trace-paste" />
</head>
<body>
<main class="page-shell">
@@ -127,6 +127,6 @@ Or paste a Scattermind Concept Map JSON object here; Ranker will preserve source
</main>
<div class="toast" id="toast" hidden></div>
<script src="/app.js?v=2.2.0-feedback-front-door" type="module"></script>
<script src="/app.js?v=2.2.1-source-trace-paste" type="module"></script>
</body>
</html>
+1 -1
View File
@@ -39,4 +39,4 @@ button,input,textarea{font:inherit} button{cursor:pointer} a{color:inherit;text-
.lane-column .rank-card{box-shadow:none;margin:0;border-width:1.5px}.lane-column .rank-card h3{font-size:clamp(20px,2vw,28px)}.lane-column .metrics{grid-template-columns:1fr}.signal-pills{margin:10px 0}.signal-pills span{background:#f0e8d9;font-size:10px;padding:5px 8px}.action-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:14px 0}.action-strip>div{border:1.5px solid var(--hair);background:#fff6e6;padding:10px}.action-strip span{color:var(--blue2);font-size:10px}.action-strip p{margin:0;font-size:13px;line-height:1.35}.reflection-room{margin-top:24px}.expert-card{position:relative}.expert-card::before{content:"“";position:absolute;right:14px;top:0;font-size:72px;line-height:1;color:rgba(36,92,255,.16);font-weight:1000}
@media (max-width:1100px){.lane-board{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-glance{grid-template-columns:repeat(2,minmax(0,1fr))}.action-strip{grid-template-columns:1fr}}
@media (max-width:700px){.lane-board,.quick-glance{grid-template-columns:1fr}.memo-head::after{position:static;display:inline-block;margin-top:14px;transform:none}.result-actions .button{width:100%;justify-content:center}.quick-glance{box-shadow:6px 6px 0 var(--ink)}.quick-glance>div{min-height:auto}}
.source-trace{margin:-4px 0 22px;padding:14px 16px;border:2px dashed var(--ink);background:#fff6e5;color:var(--ink2);box-shadow:5px 5px 0 rgba(21,19,15,.16)}.source-trace span,.handoff-card>span{display:block;margin-bottom:8px;color:var(--blue2);text-transform:uppercase;letter-spacing:.12em;font-size:11px;font-weight:1000}.source-trace b{display:block;margin-bottom:6px;font-size:14px}.source-trace p{margin:0;color:var(--muted);line-height:1.45}.handoff-card{border-left:8px solid var(--green)}.handoff-card.status-usable-with-warnings{border-left-color:var(--amber)}.handoff-card.status-needs-source-context,.handoff-card.status-blocked{border-left-color:var(--red)}.handoff-warnings{display:grid;gap:6px;margin-top:12px}.handoff-warnings code{display:block;white-space:normal;border:1px solid var(--hair);background:#f3eee4;padding:7px 9px;font-size:12px;color:var(--ink2)}
.source-trace{margin:-4px 0 22px;padding:14px 16px;border:2px dashed var(--ink);background:#fff6e5;color:var(--ink2);box-shadow:5px 5px 0 rgba(21,19,15,.16)}.source-trace span,.handoff-card>span{display:block;margin-bottom:8px;color:var(--blue2);text-transform:uppercase;letter-spacing:.12em;font-size:11px;font-weight:1000}.source-trace b{display:block;margin-bottom:6px;font-size:14px}.source-trace p{margin:0;color:var(--muted);line-height:1.45}.item-source-trace{margin:12px 0;padding:9px 10px;border:1.5px dashed var(--hair);background:#fffaf1;color:var(--ink2)}.item-source-trace summary{cursor:pointer;color:var(--blue2);font-size:11px;font-weight:1000;letter-spacing:.08em;text-transform:uppercase}.item-source-trace p{margin:8px 0 6px;color:var(--muted);font-size:13px;line-height:1.4}.item-source-trace small{display:block;color:var(--muted);font-size:11px;font-weight:800}.handoff-card{border-left:8px solid var(--green)}.handoff-card.status-usable-with-warnings{border-left-color:var(--amber)}.handoff-card.status-needs-source-context,.handoff-card.status-blocked{border-left-color:var(--red)}.handoff-warnings{display:grid;gap:6px;margin-top:12px}.handoff-warnings code{display:block;white-space:normal;border:1px solid var(--hair);background:#f3eee4;padding:7px 9px;font-size:12px;color:var(--ink2)}