import React, { useState } from 'react'; import Icon from './CanvasIcon'; const fireToast = (msg, kind = 'success') => window.dispatchEvent(new CustomEvent('canvas-toast', { detail: { msg, kind } })); // Critic widgets are scripted in canvas; the *real* critique should happen in // the main chat so message history lives in one place (per Daniel's review). // "Open in chat" stashes a draft prompt + persona hint then asks CanvasPage to // navigate to chat — the chat page can read `canvas-chat-handoff` from localStorage. const handoffToChat = (persona, prompt, context = {}) => { try { localStorage.setItem('canvas-chat-handoff', JSON.stringify({ at: Date.now(), persona, prompt, ...context, })); } catch { /* ignore */ } window.dispatchEvent(new CustomEvent('canvas-open-in-chat', { detail: { persona, prompt } })); fireToast(`Opening ${persona} in chat — full history will be there.`); }; // ---------- Reviewer 2 widget ---------- export function Reviewer2Widget({ state, setState, openModal }) { return ( <>
"Paste a draft paragraph, abstract, or section. I'll respond as the most uncharitable but technically competent reviewer your work will ever see."
{state.lastReview ? (
Last critique · {state.lastReview.severity}/10 severity
Major: {state.lastReview.major}
+{state.lastReview.minorCount} minor issues, {state.lastReview.suggestionCount} suggestions
) : (
No drafts reviewed yet.
)}
tone
harsh
); } // ---------- Devil's Advocate widget ---------- export function DevilsAdvocateWidget({ state, setState, openModal }) { return ( <>
Your claim
"{state.claim}"
Strongest counters · {state.counters.length}
{state.counters.slice(0, 3).map((c, i) => (
{c.lbl}
{c.text}
))}
); } // ---------- Scope realism widget ---------- export function ScopeRealismWidget({ state, openModal }) { return (
{state.score.toFixed(1)}
Feasibility
{state.label}
target: {state.target}
{state.factors.map(f => (
{f.label} {f.val}
))}
); } // =================================================================== // Critic modals // =================================================================== const REVIEW_TEMPLATES = [ { severity: 8, major: 'The program states a goal ("get bigger and stronger") before defining what success looks like. You never specify the measurable target — which lifts, how much, by when — so there is no way to tell if the plan is working or when to change it.', minor: [ 'Weekly volume is described as "high" without a set count per muscle group.', 'Progression is listed as "add weight when you can" with no concrete rule (reps in reserve? double progression?).', 'Legs get one day while chest gets two, with no rationale for the imbalance.', 'No deload is scheduled across a 12-week block — fatigue will accumulate unmanaged.', ], suggestions: [ 'Add one sentence naming the target: e.g. "bench 100kg for 5, add 5cm on arms in 12 weeks."', 'Pick an explicit progression rule and write it down.', 'Schedule a deload every 5–6 weeks and rebalance leg volume.', ], }, { severity: 7, major: 'You claim this split is "optimal for hypertrophy." That word is doing too much work. The layout is also compatible with several worse outcomes (junk volume, under-recovery, skipped legs). Without a way to check whether volume actually drives growth for you, "optimal" is untestable.', minor: [ 'The split is justified by "that\'s what most lifters do" rather than by your schedule and recovery.', 'No statement of what would make you change the plan.', 'A program with no logged numbers is a red flag — you can\'t audit it later.', ], suggestions: [ 'Replace "optimal" with a specific target the plan either hits or misses.', 'List 1–2 signals (stalled lifts, poor sleep) that would trigger a change.', ], }, { severity: 9, major: 'This reads like a wish list, not a plan. There is no number. There is no timeframe. The strongest claim is that you want to "tone up and feel better" — which is the lowest possible bar. If you actually care about the result, lead with the measurable goal, not the vibe.', minor: [ 'The word "consistent" appears three times in three sentences. Show it with a logged history instead.', '"Functional training" is jargon-without-definition; say what you\'ll actually do.', 'No mention of nutrition, despite it driving most of the outcome you want.', ], suggestions: [ 'Lead sentence: "In 12 weeks I will ."', 'Cut "I want to get in shape" entirely. Replace it with a lift, a weight, and a date.', ], }, ]; export function ReviewerModal({ data, onClose }) { const [draft, setDraft] = useState(data.initial || ''); const [running, setRunning] = useState(false); const [review, setReview] = useState(null); const run = () => { if (!draft.trim()) return; setRunning(true); setReview(null); setTimeout(() => { const idx = (draft.length + draft.split(' ').length) % REVIEW_TEMPLATES.length; const r = REVIEW_TEMPLATES[idx]; setReview(r); setRunning(false); }, 900); }; const accept = () => { if (!review) return; data.onComplete({ draft, severity: review.severity, major: review.major, minorCount: review.minor.length, suggestionCount: review.suggestions.length, }); fireToast('Critique saved · severity ' + review.severity + '/10', 'critic'); onClose(); }; return (
e.stopPropagation()}>
Reviewer 2 Simulator
Paste a draft. Get the harshest competent peer review you'll ever read — before a real reviewer does.