diff --git a/README.md b/README.md index fae73ef..4803577 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Ranker's continuation job is narrow: Candidate items may include optional 1–10 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, inside `conceptMap.context`, or as a structured top-level `context` object with `summary`, `targetAudience`, `constraints`, `nonGoals` / `avoid`, and `assumptions`; Ranker merges these sources rather than letting a shallow wrapper context shadow deeper Concept Map guardrails. Lens-only Concept Maps may additionally send audience / constraints / assumptions / risk lens content, and Ranker will split sentence-style lens notes into readable decision context instead of leaking `[object Object]`. If Scattermind only has a flat context string, Ranker now extracts simple guardrails such as `Solo builder`, `Manual proof`, `Avoid ...`, `No ...`, and `Do not ...` into `input.decisionContext` / `handoff.decisionContext` so early bridge exports still protect against dashboard/auth/billing drift. Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable. Ranker also accepts Scattermind's paid Concept Map object directly when it arrives with top-level `reference_code`, `working_name`, `ideaText`, and string-valued `lenses.channel` / `lenses.risk` fields; the reference code becomes source provenance, the working name becomes the source title, and labelled Build Order text is turned into rank-ready candidates without requiring Scattermind to wrap it first. -Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. +Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen. Recommended payload shape: diff --git a/public/app.js b/public/app.js index 7d64081..9944379 100644 --- a/public/app.js +++ b/public/app.js @@ -51,6 +51,31 @@ 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], @@ -227,8 +252,8 @@ function renderResults(data) { async function createFeedbackMap(event) { event.preventDefault(); const submit = form.querySelector('button[type="submit"]'); - const payload = Object.fromEntries(new FormData(form).entries()); - if (!String(payload.optionsText || '').trim()) payload.optionsText = payload.idea; + 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 { diff --git a/public/index.html b/public/index.html index 4c38ade..228b9a5 100644 --- a/public/index.html +++ b/public/index.html @@ -59,13 +59,15 @@
MVP · feedback front door · no account · no dashboard swamp
Paste the backlog, rough notes, feature dump, or possible next moves. Ranker will extract candidates when it can; use the optional candidate box only if you already have a cleaner list.
+Paste the backlog, rough notes, feature dump, possible next moves, or a raw Scattermind Concept Map JSON export. Ranker will extract candidates when it can; use the optional candidate box only if you already have a cleaner list.