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();
if (!text.startsWith('{') || !text.endsWith('}')) return null;
try {
const parsed = JSON.parse(text);
const looksLikeBridgePayload = parsed && typeof parsed === 'object' && !Array.isArray(parsed) && (
parsed.schema || parsed.featureSet || parsed.conceptMap || parsed.lenses || parsed.reference_code || parsed.referenceCode || parsed.artifactId || parsed.ideaText
);
return looksLikeBridgePayload ? parsed : null;
} catch {
return null;
}
}
function payloadFromForm(formPayload) {
const ideaJson = parsePastedJsonPayload(formPayload.idea);
const optionsJson = parsePastedJsonPayload(formPayload.optionsText);
const embedded = ideaJson || optionsJson;
if (!embedded) return formPayload;
const merged = { ...embedded };
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 `
${items.map(([label, value]) => `
${label}
`).join('')}
`;
}
function renderPills(items = []) {
if (!items.length) return '';
return `${items.map((item) => `${escapeHtml(item)}`).join('')}
`;
}
function renderRankCard(item) {
return `
#${item.rank}
${escapeHtml(item.lane?.label || 'Ranked')}
${item.metrics.score}
${escapeHtml(item.title)}
${item.description && item.description !== item.title ? `${escapeHtml(item.description)}
` : ''}
${renderPills(item.scoreDrivers || [])}
Why: ${escapeHtml(item.reason)}.
Concern: ${escapeHtml(item.concern)}
Next step${escapeHtml(item.nextStep || 'Run the smallest proof step.')}
Evidence question${escapeHtml(item.evidenceQuestion || 'What proof would change this ranking?')}
Success / kill signal${escapeHtml(item.successSignal || '')} ${escapeHtml(item.killSignal ? `Kill if: ${item.killSignal}` : '')}
${renderMetrics(item.metrics)}
${renderPills(item.scoringNotes || [])}
`;
}
function renderLane(ranked, laneId) {
const items = ranked.filter((item) => item.lane?.id === laneId);
const meta = laneMeta[laneId];
return `
${escapeHtml(meta.title)}
${items.length}
${escapeHtml(meta.note)}
${items.length ? items.map(renderRankCard).join('') : 'No items landed here.'}
`;
}
function markdownBrief(data) {
const brief = data.brief || {};
const glance = brief.quickGlance || {};
const ranked = data.ranked || [];
const lanes = ['do', 'test', 'defer', 'park'];
const laneTitles = { do: 'Do first', test: 'Validate next', defer: 'Defer', park: 'Park / cut' };
return [
`# ${brief.headline || 'Ranked feedback map'}`,
'',
brief.summary || '',
'',
'## Quick judgement',
`- Top pick: ${glance.topPick || 'n/a'}`,
`- Why it wins: ${glance.whyThisWins || 'n/a'}`,
`- Next action: ${glance.nextAction || 'n/a'}`,
`- Evidence question: ${glance.evidenceQuestion || 'n/a'}`,
`- Biggest trap: ${glance.biggestTrap || 'n/a'}`,
`- Confidence: ${data.rankConfidence?.level || 'n/a'} — ${data.rankConfidence?.reason || ''}`,
'',
...lanes.flatMap((lane) => {
const items = ranked.filter((item) => item.lane?.id === lane);
return [`## ${laneTitles[lane]}`, ...(items.length ? items.map((item) => `- #${item.rank} ${item.title}: ${item.reason}. Next: ${item.nextStep}`) : ['- None']) , ''];
}),
'## Next 48 hours',
...(brief.next48Hours || []).map((step) => `- ${step}`),
].join('\n');
}
async function copyText(text, label) {
try {
await navigator.clipboard.writeText(text);
toast(`${label} copied.`);
} catch {
toast('Clipboard blocked. Browser being a tiny tyrant.');
}
}
function attachResultActions(data) {
document.querySelector('#copyBrief')?.addEventListener('click', () => copyText(markdownBrief(data), 'Decision brief'));
document.querySelector('#copyActions')?.addEventListener('click', () => copyText((data.brief?.next48Hours || []).map((step, index) => `${index + 1}. ${step}`).join('\n'), '48h actions'));
document.querySelector('#copyJson')?.addEventListener('click', () => copyText(JSON.stringify({ brief: data.brief, ranked: data.ranked, buildOrder: data.buildOrderDetails, handoff: data.handoff }, null, 2), 'JSON handoff'));
}
function renderResults(data) {
lastResult = data;
const ranked = data.ranked || [];
const brief = data.brief || {};
const glance = brief.quickGlance || {};
results.hidden = false;
results.innerHTML = `
${escapeHtml(data.mode?.label || 'Ranked feedback')} · first-pass judgement memo
${escapeHtml(brief.headline || 'Ranked feedback map')}
${escapeHtml(brief.summary || '')}
Not an oracle${escapeHtml(data.rankConfidence?.level || 'first pass')} confidence${ranked.length} items judged
Do this first${escapeHtml(glance.topPick || 'Add options')}
Why it wins${escapeHtml(glance.whyThisWins || 'The list needs more comparable options.')}
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.')}
${['do', 'test', 'defer', 'park'].map((lane) => renderLane(ranked, lane)).join('')}
Next 48 hours
${(brief.next48Hours || []).map((step) => `- ${escapeHtml(step)}
`).join('')}
Rank confidence
${escapeHtml(data.rankConfidence?.level || 'First pass')} — ${escapeHtml(data.rankConfidence?.reason || brief.caution || '')}
${(brief.whatWouldChangeRanking || []).length ? `
What would change the order
${brief.whatWouldChangeRanking.map((change) => `- ${escapeHtml(change)}
`).join('')}
` : ''}
${(data.closeCalls || []).length ? `
Close calls
${data.closeCalls.map((call) => `- ${escapeHtml(call.note)}
`).join('')}
` : ''}
${(brief.assumptions || []).length ? `
Context carried forward
${brief.assumptions.map((assumption) => `- ${escapeHtml(assumption)}
`).join('')}
` : ''}
${(brief.expertReflections || []).map((reflection) => `
${escapeHtml(reflection.lens)}
${escapeHtml(reflection.text)}
`).join('')}
${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);