From 7155e6cd7ddf25a5a4d4f6d6abecd49f0cf519dc Mon Sep 17 00:00:00 2001 From: soryu Date: Tue, 20 Jan 2026 23:20:32 +0000 Subject: Add non-blocking persistent contract completion questions (#14) * [WIP] Heartbeat checkpoint - 2026-01-20 22:40:37 UTC * Task completion checkpoint --- .../components/mesh/ContractCompleteQuestion.tsx | 165 +++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 makima/frontend/src/components/mesh/ContractCompleteQuestion.tsx (limited to 'makima/frontend/src/components/mesh') diff --git a/makima/frontend/src/components/mesh/ContractCompleteQuestion.tsx b/makima/frontend/src/components/mesh/ContractCompleteQuestion.tsx new file mode 100644 index 0000000..d4ef618 --- /dev/null +++ b/makima/frontend/src/components/mesh/ContractCompleteQuestion.tsx @@ -0,0 +1,165 @@ +import { useState } from "react"; +import type { PendingQuestion } from "../../lib/api"; + +interface ContractCompleteQuestionProps { + question: PendingQuestion; + onAnswer: (questionId: string, response: string) => Promise; +} + +/** + * Component for displaying contract_complete type questions prominently on the task page. + * These questions persist until answered and are not shown as floating notifications. + */ +export function ContractCompleteQuestion({ + question, + onAnswer, +}: ContractCompleteQuestionProps) { + const [submitting, setSubmitting] = useState(false); + const [minimized, setMinimized] = useState(false); + const [customInput, setCustomInput] = useState(""); + const [showCustom, setShowCustom] = useState(false); + + const handleAnswer = async (response: string) => { + if (submitting) return; + setSubmitting(true); + try { + await onAnswer(question.questionId, response); + } finally { + setSubmitting(false); + } + }; + + const handleCustomSubmit = async () => { + if (!customInput.trim() || submitting) return; + await handleAnswer(customInput.trim()); + setCustomInput(""); + setShowCustom(false); + }; + + // Default choices for contract completion questions + const defaultChoices = + question.choices.length > 0 + ? question.choices + : ["Yes, contract is complete", "No, more work needed"]; + + if (minimized) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+ ? +
+
+

+ Contract Completion Review +

+

+ Please review and respond +

+
+
+ +
+ + {/* Content */} +
+ {/* Context */} + {question.context && ( +
+ {question.context} +
+ )} + + {/* Question */} +
+ {question.question} +
+ + {/* Choices */} +
+ {defaultChoices.map((choice, idx) => ( + + ))} +
+ + {/* Custom input option */} + {!showCustom ? ( + + ) : ( +
+ setCustomInput(e.target.value)} + placeholder="Type your response..." + disabled={submitting} + className="flex-1 px-3 py-2 bg-[#0a1525] border border-green-500/30 text-green-100 text-sm font-mono rounded focus:outline-none focus:border-green-400" + onKeyDown={(e) => { + if (e.key === "Enter" && customInput.trim()) { + handleCustomSubmit(); + } + if (e.key === "Escape") { + setShowCustom(false); + setCustomInput(""); + } + }} + /> + + +
+ )} +
+
+ ); +} -- cgit v1.2.3