From 05ba47255f7c53afa08b001d2d62d3f321eb452e Mon Sep 17 00:00:00 2001 From: soryu Date: Tue, 20 Jan 2026 22:40:37 +0000 Subject: [WIP] Heartbeat checkpoint - 2026-01-20 22:40:37 UTC --- .../components/SupervisorQuestionNotification.tsx | 9 +- .../components/mesh/ContractCompleteQuestion.tsx | 165 +++++++++++++++++++++ makima/frontend/src/lib/api.ts | 4 +- makima/frontend/src/routes/mesh.tsx | 63 +++++--- 4 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 makima/frontend/src/components/mesh/ContractCompleteQuestion.tsx (limited to 'makima/frontend') diff --git a/makima/frontend/src/components/SupervisorQuestionNotification.tsx b/makima/frontend/src/components/SupervisorQuestionNotification.tsx index 1457d86..b1cbacc 100644 --- a/makima/frontend/src/components/SupervisorQuestionNotification.tsx +++ b/makima/frontend/src/components/SupervisorQuestionNotification.tsx @@ -5,7 +5,12 @@ export function SupervisorQuestionNotification() { const navigate = useNavigate(); const { notificationQuestions, dismissNotification } = useSupervisorQuestions(); - if (notificationQuestions.length === 0) { + // Filter out contract_complete questions - they are displayed on the task page instead + const filteredQuestions = notificationQuestions.filter( + (q) => q.questionType !== "contract_complete" + ); + + if (filteredQuestions.length === 0) { return null; } @@ -16,7 +21,7 @@ export function SupervisorQuestionNotification() { return (
- {notificationQuestions.map((question) => ( + {filteredQuestions.map((question) => (
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(""); + } + }} + /> + + +
+ )} +
+
+ ); +} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 78e52cd..14ec9f2 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -1948,8 +1948,8 @@ export interface PendingQuestion { createdAt: string; /** Whether multiple choices can be selected */ multiSelect?: boolean; - /** Question type - "general" for regular questions, "phase_confirmation" for phase transitions */ - questionType?: "general" | "phase_confirmation"; + /** Question type - "general" for regular questions, "phase_confirmation" for phase transitions, "contract_complete" for contract completion */ + questionType?: "general" | "phase_confirmation" | "contract_complete"; /** Phase confirmation specific data (when questionType is "phase_confirmation") */ phaseConfirmation?: { currentPhase: ContractPhase; diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx index a8d3574..142cc54 100644 --- a/makima/frontend/src/routes/mesh.tsx +++ b/makima/frontend/src/routes/mesh.tsx @@ -5,6 +5,7 @@ import { TaskList } from "../components/mesh/TaskList"; import { TaskDetail } from "../components/mesh/TaskDetail"; import { TaskOutput } from "../components/mesh/TaskOutput"; import { UnifiedMeshChatInput } from "../components/mesh/UnifiedMeshChatInput"; +import { ContractCompleteQuestion } from "../components/mesh/ContractCompleteQuestion"; import { useTasks } from "../hooks/useTasks"; import { useTaskSubscription, type TaskUpdateEvent, type TaskOutputEvent } from "../hooks/useTaskSubscription"; import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary } from "../lib/api"; @@ -89,6 +90,14 @@ export default function MeshPage() { [pendingQuestions] ); + // Filter contract_complete questions for the current task + const contractCompleteQuestionsForTask = useMemo( + () => pendingQuestions.filter( + (q) => q.questionType === "contract_complete" && q.taskId === id + ), + [pendingQuestions, id] + ); + // Handler for answering supervisor questions const handleAnswerQuestion = useCallback(async (questionId: string, response: string) => { await submitAnswer(questionId, response); @@ -751,27 +760,41 @@ export default function MeshPage() { {/* Output panel */} {(viewMode === "split" || viewMode === "output") && (
- { - setViewingSubtaskId(null); - setViewingSubtaskName(null); - } : undefined} - onClear={() => { - setTaskOutputEntries([]); - if (activeOutputTaskId) { - clearPersistedOutput(activeOutputTaskId); - } - }} - taskId={activeOutputTaskId} - onUserInput={handleUserInput} - pendingQuestionIds={pendingQuestionIds} - onAnswerQuestion={handleAnswerQuestion} - /> + {/* Contract complete questions - shown prominently at top */} + {contractCompleteQuestionsForTask.length > 0 && ( +
+ {contractCompleteQuestionsForTask.map((question) => ( + + ))} +
+ )} +
+ { + setViewingSubtaskId(null); + setViewingSubtaskName(null); + } : undefined} + onClear={() => { + setTaskOutputEntries([]); + if (activeOutputTaskId) { + clearPersistedOutput(activeOutputTaskId); + } + }} + taskId={activeOutputTaskId} + onUserInput={handleUserInput} + pendingQuestionIds={pendingQuestionIds} + onAnswerQuestion={handleAnswerQuestion} + /> +
)}
-- cgit v1.2.3