From 4e1b36d7b428fb7ac2979356e0ddbd18af4ff131 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 27 May 2026 19:14:04 +0200 Subject: [PATCH] Add active slice first-screen receipt --- public/app.js | 28 ++++++++++++++++++++++++++++ public/styles.css | 5 +++++ scripts/check-rank-feedback.mjs | 6 ++++++ server.js | 14 +++++++++++++- 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/public/app.js b/public/app.js index 38eda12..36853c7 100644 --- a/public/app.js +++ b/public/app.js @@ -196,6 +196,33 @@ function renderHandoffStatus(handoff = {}) { `; } +function renderFirstScreen(firstScreen = {}) { + if (!firstScreen.headline) return ''; + const held = firstScreen.holdBack || []; + const guardrails = firstScreen.guardrails || []; + return ` +
+
+ One thing to do now +

${escapeHtml(firstScreen.headline)}

+

${escapeHtml(firstScreen.primaryAction || 'Run the smallest manual proof before product machinery.')}

+
+
+ Proof question +

${escapeHtml(firstScreen.proofQuestion || 'What evidence would change this order?')}

+
+
+ Why this wins +

${escapeHtml(firstScreen.why || 'It is the best first proof slice.')}

+
+ ${held.length ? `
Hold back
    ${held.map((item) => `
  • ${escapeHtml(item.title)}${item.lane ? ` — ${escapeHtml(item.lane)}` : ''}
  • `).join('')}
` : ''} + ${guardrails.length ? `Guardrails: ${guardrails.map(escapeHtml).join(' · ')}` : ''} + ${firstScreen.sourceAnchor ? `Source anchor: ${escapeHtml(firstScreen.sourceAnchor)}` : ''} + ${escapeHtml(firstScreen.rule || 'One active move. Everything else waits.')} +
+ `; +} + function renderDecisionReceipt(receipt = {}) { if (!receipt.activeMove) return ''; const held = receipt.doNotStartYet || []; @@ -348,6 +375,7 @@ function renderResults(data) {
Proof to collect

${escapeHtml(glance.evidenceQuestion || 'Name the evidence that would change the ranking.')}

Trap to avoid

${escapeHtml(glance.biggestTrap || brief.caution || 'Do not treat first-pass judgement as final truth.')}

+ ${renderFirstScreen(brief.firstScreen || {})} ${renderDecisionReceipt(brief.decisionReceipt || {})} ${renderSourceTrace(glance.sourceTrace || {})} diff --git a/public/styles.css b/public/styles.css index eaf7f5d..41f1e9b 100644 --- a/public/styles.css +++ b/public/styles.css @@ -42,3 +42,8 @@ button,input,textarea{font:inherit} button{cursor:pointer} a{color:inherit;text- .decision-receipt{display:grid;grid-template-columns:1.1fr 1fr 1fr 1fr;gap:10px;margin:-6px 0 22px;padding:12px;border:2px solid var(--ink);background:#fffaf1;box-shadow:6px 6px 0 rgba(21,19,15,.18)}.decision-receipt>div{border:1.5px solid var(--hair);background:#fff6e5;padding:12px}.decision-receipt span{display:block;margin-bottom:7px;color:var(--blue2);text-transform:uppercase;letter-spacing:.12em;font-size:10px;font-weight:1000}.decision-receipt strong{display:block;font-size:clamp(20px,2.3vw,30px);line-height:1;letter-spacing:-.05em}.decision-receipt p{margin:0;color:var(--ink2);line-height:1.35}.decision-receipt ul{margin:0;padding-left:18px;color:var(--ink2)}.decision-receipt small{grid-column:1/-1;color:var(--muted);font-weight:850}.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)} @media (max-width:1100px){.decision-receipt{grid-template-columns:repeat(2,minmax(0,1fr))}} @media (max-width:700px){.decision-receipt{grid-template-columns:1fr;box-shadow:5px 5px 0 rgba(21,19,15,.18)}} +.active-slice-strip{display:grid;grid-template-columns:1.25fr 1fr 1fr 1fr;gap:10px;margin:-6px 0 22px;padding:12px;border:3px solid var(--ink);background:#15130f;color:#fff;box-shadow:8px 8px 0 var(--blue)} +.active-slice-strip>div{border:1.5px solid rgba(255,255,255,.32);background:linear-gradient(145deg,rgba(255,255,255,.10),rgba(255,255,255,.03));padding:13px} +.active-slice-strip span{display:block;margin-bottom:7px;color:#bfcaff;text-transform:uppercase;letter-spacing:.12em;font-size:10px;font-weight:1000}.active-slice-strip h3{margin:0 0 8px;font-size:clamp(24px,3vw,42px);line-height:.9;letter-spacing:-.06em}.active-slice-strip p{margin:0;color:#f7efe1;line-height:1.38}.active-slice-strip ul{margin:0;padding-left:18px;color:#f7efe1}.active-slice-strip small{grid-column:1/-1;color:#d9ddff;font-weight:850}.active-slice-main{background:linear-gradient(145deg,rgba(36,92,255,.28),rgba(255,255,255,.04))!important} +@media (max-width:1100px){.active-slice-strip{grid-template-columns:repeat(2,minmax(0,1fr))}} +@media (max-width:700px){.active-slice-strip{grid-template-columns:1fr;box-shadow:5px 5px 0 var(--blue)}} diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index 0f37001..2c62e13 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -81,6 +81,12 @@ try { assert.ok(data.ranked.find(item => item.id === 'bridge-contract').factors.metricHints.value === undefined); assert.equal(data.brief.source.artifactId, 'snapshot_123'); assert.equal(data.brief.decisionReceipt.activeMove, data.brief.quickGlance.topPick); + assert.match(data.brief.firstScreen.headline, /Build only this first/); + assert.equal(data.brief.firstScreen.primaryAction, data.brief.decisionReceipt.firstProofStep); + assert.equal(data.brief.firstScreen.proofQuestion, data.brief.decisionReceipt.evidenceQuestion); + assert.deepEqual(data.brief.firstScreen.guardrails, ['Avoid accounts, workspaces, and team voting']); + assert.ok(data.brief.firstScreen.holdBack.some(item => item.title === 'Subscription billing layer')); + assert.match(data.brief.firstScreen.rule, /One active move/); assert.match(data.brief.decisionReceipt.firstProofStep, /manual proof/i); assert.deepEqual(data.brief.decisionReceipt.doNotStartYet.slice(0, 2), ['Subscription billing layer', 'Accounts and saved workspaces']); assert.match(data.brief.decisionReceipt.sourceAnchor, /concept-map\.nextMoves/); diff --git a/server.js b/server.js index 519a922..60e1156 100644 --- a/server.js +++ b/server.js @@ -1622,13 +1622,24 @@ function createDecisionBrief({ idea, context, mode, ranked, provenance, decision sourceQuote: top.provenance?.sourceQuote || '', }, } : null; + const activeSourceAnchor = [top?.provenance?.sourceSection, top?.provenance?.sourceId || top?.provenance?.sourceTitle].filter(Boolean).join(' · '); + const firstScreen = top ? { + headline: `Build only this first: ${top.title}`, + primaryAction: nextStepFor(top), + proofQuestion: evidenceQuestionFor(top), + why: reasonFor(top), + sourceAnchor: activeSourceAnchor, + holdBack: deferred.slice(0, 3).map(item => ({ title: item.title, lane: item.lane?.label || 'Not now', reason: reasonFor(item) })), + guardrails: (decisionContext?.nonGoals || []).slice(0, 3), + rule: 'One active move. Everything else waits until this proof produces evidence.', + } : null; const decisionReceipt = top ? { activeMove: top.title, activeLane: top.lane?.label || 'Do first', firstProofStep: nextStepFor(top), evidenceQuestion: evidenceQuestionFor(top), doNotStartYet: deferred.slice(0, 3).map(item => item.title), - sourceAnchor: [top.provenance?.sourceSection, top.provenance?.sourceId || top.provenance?.sourceTitle].filter(Boolean).join(' · '), + sourceAnchor: activeSourceAnchor, handoffRule: 'Only the Do first item is active. Validate one proof, then re-rank before promoting anything else.', } : null; const assumptions = [ @@ -1640,6 +1651,7 @@ function createDecisionBrief({ idea, context, mode, ranked, provenance, decision headline: top ? `Start with ${top.title}` : 'Add options to get a ranked feedback map', summary: `${theme}${second ? ` “${second.title}” is the nearest follow-up, not a parallel first step.` : ''}${sourceLabel ? ` Source: ${sourceLabel}.` : ''}`, quickGlance, + firstScreen, decisionReceipt, source: provenance ? { schema: provenance.schema,