diff --git a/src/App.tsx b/src/App.tsx index e9bff77..fdf5b53 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -143,6 +143,7 @@ function App() { const [triageError, setTriageError] = useState('') const [triageBatchStatus, setTriageBatchStatus] = useState<'idle' | 'running'>('idle') const [triageEditMode, setTriageEditMode] = useState(false) + const [triageClarificationAnswer, setTriageClarificationAnswer] = useState('') const [triageSavedMessage, setTriageSavedMessage] = useState('') const [showManualFeatureEditor, setShowManualFeatureEditor] = useState(false) const [showManualParkingEditor, setShowManualParkingEditor] = useState(false) @@ -656,6 +657,7 @@ function App() { setTriageOpen(true) setTriageError('') setTriageEditMode(false) + setTriageClarificationAnswer('') setTriageSavedMessage('') setTriageStatus(triageRecommendation && !seed ? 'ready' : 'idle') if (seed) { @@ -671,6 +673,7 @@ function App() { setTriageStatus('idle') setTriageError('') setTriageEditMode(false) + setTriageClarificationAnswer('') } const openDecisionPulse = (pulseId?: string | null) => { @@ -741,7 +744,7 @@ function App() { .map((pulse) => ({ message: pulse.message, evidence_refs: pulse.evidence_refs })), }) - const runAiTriage = async () => { + const runAiTriage = async (optionalContextOverride?: string) => { const rawIdea = triageDraft.rawIdea.trim() if (!rawIdea) { setTriageError('Write the idea first. The scope goblin needs bait.') @@ -749,6 +752,8 @@ function App() { return } + const optionalContext = optionalContextOverride ?? triageDraft.optionalContext.trim() + setTriageStatus('loading') setTriageError('') setTriageSavedMessage('') @@ -757,7 +762,7 @@ function App() { try { const recommendation = await triageIdeaWithAi({ raw_idea: rawIdea, - optional_context: triageDraft.optionalContext.trim(), + optional_context: optionalContext, app_context: buildAiTriageContext(), }) const timestamp = nowIso() @@ -765,7 +770,7 @@ function App() { id: `rec_${slugify(rawIdea)}_${timestamp.replace(/[^0-9]/g, '')}`, created_at: timestamp, raw_idea: rawIdea, - optional_context: triageDraft.optionalContext.trim(), + optional_context: optionalContext, context_summary: `${appState.project.name}: ${appState.project.current_goal}`, ...recommendation, user_decision: 'pending', @@ -786,6 +791,7 @@ function App() { }) setTriageStatus('ready') setTriageEditMode(false) + setTriageClarificationAnswer('') setStatusMessage(`AI suggested ${placementLabels[fullRecommendation.suggested_placement]} with ${fullRecommendation.scope_risk} scope risk.`) } catch (error) { setTriageStatus('error') @@ -1203,6 +1209,28 @@ function App() { setStatusMessage('Pulse deleted.') } + const answerClarifyingQuestion = async () => { + const answer = triageClarificationAnswer.trim() + if (!answer) { + setTriageError('Give the AI a real answer first.') + setTriageStatus('error') + return + } + + const mergedContext = [triageDraft.optionalContext.trim(), `Clarification answer: ${answer}`] + .filter(Boolean) + .join('\n\n') + + setTriageDraft((current) => ({ + ...current, + optionalContext: mergedContext, + })) + setTriageRecommendation(null) + setTriageEditMode(false) + setStatusMessage('Re-running triage with your clarification…') + await runAiTriage(mergedContext) + } + const copyMarkdown = async (filename: string) => { try { await navigator.clipboard.writeText(markdownPackage[filename as keyof typeof markdownPackage]) @@ -1274,8 +1302,10 @@ function App() { ? `${target} handoff for “${feature.title}” copied${shouldLogIntent ? ' and INTENT logged' : ''}.` : `${target} handoff copied${shouldLogIntent ? ' and INTENT logged' : ''}.`, ) + return true } catch { setStatusMessage('Clipboard copy failed. Browser said no.') + return false } } @@ -1292,9 +1322,15 @@ function App() { } const syncHandoffPreviewTarget = (target: (typeof PROMPT_TARGETS)[number]) => { - const { prompt } = buildHandoffPrompt(handoffPreviewFeatureId || undefined, target) + const { prompt } = buildHandoffPrompt(handoffPreviewFeatureId || undefined, handoffPreviewTarget) + if (handoffPreviewDraft.trim() !== prompt.trim()) { + const shouldReplace = window.confirm('Switching target will replace your current edits with the new preset. Continue?') + if (!shouldReplace) return + } + + const { prompt: nextPrompt } = buildHandoffPrompt(handoffPreviewFeatureId || undefined, target) setHandoffPreviewTarget(target) - setHandoffPreviewDraft(prompt) + setHandoffPreviewDraft(nextPrompt) } const resetHandoffPreview = () => { @@ -1621,16 +1657,20 @@ function App() {
Step 3 of 3
-AI advises. You decide.
+{triageNeedsClarification ? 'Answer the question, then re-run triage with the missing detail.' : 'AI advises. You decide.'}
Edit before saving
+{triageNeedsClarification ? 'Answer and refine' : 'Edit before saving'}
+ {triageNeedsClarification && ( + <> +{triageRecommendation?.clarifying_question || 'Clarify the idea, then re-run triage.'}
+Preview mode is the safer path when you want to edit wording or create a clean INTENT pulse.
+Switching targets resets the draft to that target’s preset.
Quick copy if you already know the target. Use preview when you want to tweak the brief or log an INTENT cleanly.
+Preview is the safer path. Quick-copy is for when you already know the target and do not need edits.
+Quick copy targets