${escapeHtml(readiness.label || readiness.status)} — ${escapeHtml(readiness.summary || '')}
${(readiness.nextChecks || []).length ? `- ${readiness.nextChecks.map((step) => `
- ${escapeHtml(step)} `).join('')}
${escapeHtml(warning)}`).join('')}const sample = { idea: 'Scattermind clarified a messy course idea. Now I need feedback on the feature/functionality order, not a dashboard.', optionsText: `- Manual build-order preview from one Concept Map - Copyable decision brief with Do first / Validate next / Defer / Park - Evidence questions beside each next move - Accounts and saved workspaces - Team voting on roadmap priority - Subscription billing layer - Polished export for sharing the defended order`, context: 'Snapshot / Concept Map handoff, solo builder, tired non-AI-native user, avoid auth/workspaces/billing before proof.', mode: 'mvp', }; const laneMeta = { do: { title: 'Do first', note: 'The active first move. Give it oxygen before the backlog starts breeding.' }, test: { title: 'Validate next', note: 'Strong candidates, but they need evidence before build time.' }, defer: { title: 'Defer', note: 'Useful later. Dangerous if it steals attention now.' }, park: { title: 'Park / cut', note: 'Not in the active decision. Reopen only if new evidence earns it.' }, }; const form = document.querySelector('#rankForm'); const results = document.querySelector('#results'); const toastEl = document.querySelector('#toast'); let lastResult = null; function escapeHtml(value) { return String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])); } function metricPct(value) { return Math.max(0, Math.min(100, Math.round(Number(value || 0) * 10))); } function toast(message) { toastEl.textContent = message; toastEl.hidden = false; clearTimeout(toastEl._timer); toastEl._timer = setTimeout(() => { toastEl.hidden = true; }, 2600); } function fillSample() { form.idea.value = sample.idea; form.optionsText.value = sample.optionsText; form.context.value = sample.context; form.mode.value = sample.mode; document.querySelector('#try')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); toast('Sample loaded. Run it or replace it with your own mess.'); } function laneClass(lane) { return `lane-${lane?.id || 'defer'}`; } function parsePastedJsonPayload(value) { const text = String(value || '').trim() .replace(/^```(?:json)?\s*/i, '') .replace(/```$/i, '') .trim(); const jsonText = text.startsWith('{') && text.endsWith('}') ? text : extractFirstJsonObject(text); if (!jsonText) return null; try { const parsed = JSON.parse(jsonText); const looksLikeBridgePayload = parsed && typeof parsed === 'object' && !Array.isArray(parsed) && ( parsed.schema || parsed.featureSet || parsed.feature_set || parsed.candidateSet || parsed.candidate_set || parsed.candidateFeatureSet || parsed.candidate_feature_set || parsed.rankReadyFeatureSet || parsed.rank_ready_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.rankerInput || parsed.ranker_input || parsed.rankerHandoff || parsed.ranker_handoff || parsed.rankReady || parsed.rank_ready || parsed.bridge || parsed.bridgePayload || parsed.bridge_payload || 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.recommendedActions) || Array.isArray(parsed.recommended_actions) || Array.isArray(parsed.suggestedActions) || Array.isArray(parsed.suggested_actions) || Array.isArray(parsed.nextActions) || Array.isArray(parsed.next_actions) || Array.isArray(parsed.nextMoves) || Array.isArray(parsed.next_moves) || Array.isArray(parsed.doFirst) || Array.isArray(parsed.do_first) || Array.isArray(parsed.continueFirst) || Array.isArray(parsed.continue_first) || Array.isArray(parsed.makeTangible) || Array.isArray(parsed.make_tangible) || Array.isArray(parsed.validateNext) || Array.isArray(parsed.validate_next) || Array.isArray(parsed.evidenceNext) || Array.isArray(parsed.evidence_next) || Array.isArray(parsed.tryNext) || Array.isArray(parsed.try_next) || Array.isArray(parsed.deferred) || Array.isArray(parsed.holdForLater) || Array.isArray(parsed.hold_for_later) || Array.isArray(parsed.parkingLot) || Array.isArray(parsed.parking_lot) || Array.isArray(parsed.setAside) || Array.isArray(parsed.set_aside) ); return looksLikeBridgePayload ? parsed : null; } catch { return null; } } function extractFirstJsonObject(text = '') { const start = text.indexOf('{'); if (start < 0) return ''; let depth = 0; let inString = false; let escaped = false; for (let index = start; index < text.length; index += 1) { const char = text[index]; if (escaped) { escaped = false; continue; } if (char === '\\' && inString) { escaped = true; continue; } if (char === '"') { inString = !inString; continue; } if (inString) continue; if (char === '{') depth += 1; if (char === '}') { depth -= 1; if (depth === 0) return text.slice(start, index + 1); } } return ''; } function payloadFromForm(formPayload) { const ideaJson = parsePastedJsonPayload(formPayload.idea); const optionsJson = parsePastedJsonPayload(formPayload.optionsText); const embedded = ideaJson || optionsJson; if (!embedded) return formPayload; 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; } function renderMetrics(metrics = {}) { const items = [ ['Value', metrics.value], ['Feasible', metrics.feasibility], ['Confidence', metrics.confidence], ['Revenue', metrics.revenue], ['Risk', metrics.risk], ]; return `
${escapeHtml(sourceTrace.sourceQuote)}
` : ''}${escapeHtml(trace.sourceQuote)}
` : ''} ${trace.sourceSection ? `Section: ${escapeHtml(trace.sourceSection)}` : ''}${escapeHtml(readiness.label || readiness.status)} — ${escapeHtml(readiness.summary || '')}
${(readiness.nextChecks || []).length ? `${escapeHtml(warning)}`).join('')}${escapeHtml(receipt.firstProofStep || 'Run the smallest manual proof before product machinery.')}
${escapeHtml(receipt.evidenceQuestion || 'What would make this ranking obviously right or wrong?')}
${escapeHtml(item.description)}
` : ''} ${renderPills(item.scoreDrivers || [])}Why: ${escapeHtml(item.reason)}.
Concern: ${escapeHtml(item.concern)}
${escapeHtml(item.nextStep || 'Run the smallest proof step.')}
${escapeHtml(item.evidenceQuestion || 'What proof would change this ranking?')}
${escapeHtml(item.successSignal || '')} ${escapeHtml(item.killSignal ? `Kill if: ${item.killSignal}` : '')}
${escapeHtml(meta.note)}
${escapeHtml(data.mode?.label || 'Ranked feedback')} · first-pass judgement memo
${escapeHtml(brief.summary || '')}
${escapeHtml(glance.whyThisWins || 'The list needs more comparable options.')}
${escapeHtml(glance.evidenceQuestion || 'Name the evidence that would change the ranking.')}
${escapeHtml(glance.biggestTrap || brief.caution || 'Do not treat first-pass judgement as final truth.')}
${escapeHtml(data.rankConfidence?.level || 'First pass')} — ${escapeHtml(data.rankConfidence?.reason || brief.caution || '')}
${escapeHtml(reflection.text)}
${escapeHtml(brief.caution || 'First-pass judgement, not an oracle.')}
`; attachResultActions(data); results.scrollIntoView({ behavior: 'smooth', block: 'start' }); } async function createFeedbackMap(event) { event.preventDefault(); const submit = form.querySelector('button[type="submit"]'); const payload = payloadFromForm(Object.fromEntries(new FormData(form).entries())); if (!String(payload.optionsText || '').trim() && !payload.conceptMap && !payload.featureSet && !payload.lenses) payload.optionsText = payload.idea; submit.disabled = true; submit.textContent = 'Judging…'; try { const response = await fetch('/api/rank-feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Could not rank this list.'); renderResults(data); } catch (error) { toast(error.message); } finally { submit.disabled = false; submit.textContent = 'Create ranked feedback map'; } } form.addEventListener('submit', createFeedbackMap); document.querySelector('#loadSample')?.addEventListener('click', fillSample); document.querySelector('#loadSampleTop')?.addEventListener('click', fillSample);