summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/mesh.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-20 23:20:32 +0000
committerGitHub <noreply@github.com>2026-01-20 23:20:32 +0000
commit7155e6cd7ddf25a5a4d4f6d6abecd49f0cf519dc (patch)
tree2475a951c4bcba685b010909bf4abd5351cb3623 /makima/frontend/src/routes/mesh.tsx
parent055e2c4a72e3b2331a18fdc9f8132ef990af7e38 (diff)
downloadsoryu-7155e6cd7ddf25a5a4d4f6d6abecd49f0cf519dc.tar.gz
soryu-7155e6cd7ddf25a5a4d4f6d6abecd49f0cf519dc.zip
Add non-blocking persistent contract completion questions (#14)
* [WIP] Heartbeat checkpoint - 2026-01-20 22:40:37 UTC * Task completion checkpoint
Diffstat (limited to 'makima/frontend/src/routes/mesh.tsx')
-rw-r--r--makima/frontend/src/routes/mesh.tsx63
1 files changed, 43 insertions, 20 deletions
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") && (
<div
- className="panel min-h-0 overflow-hidden flex-1"
+ className="panel min-h-0 overflow-hidden flex-1 flex flex-col"
>
- <TaskOutput
- entries={taskOutputEntries}
- isStreaming={isStreaming || taskDetail.status === "running"}
- viewingSubtaskName={viewingSubtaskName}
- onClearSubtaskView={viewingSubtaskId ? () => {
- 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 && (
+ <div className="shrink-0 px-3 pt-3">
+ {contractCompleteQuestionsForTask.map((question) => (
+ <ContractCompleteQuestion
+ key={question.questionId}
+ question={question}
+ onAnswer={handleAnswerQuestion}
+ />
+ ))}
+ </div>
+ )}
+ <div className="flex-1 min-h-0 overflow-hidden">
+ <TaskOutput
+ entries={taskOutputEntries}
+ isStreaming={isStreaming || taskDetail.status === "running"}
+ viewingSubtaskName={viewingSubtaskName}
+ onClearSubtaskView={viewingSubtaskId ? () => {
+ setViewingSubtaskId(null);
+ setViewingSubtaskName(null);
+ } : undefined}
+ onClear={() => {
+ setTaskOutputEntries([]);
+ if (activeOutputTaskId) {
+ clearPersistedOutput(activeOutputTaskId);
+ }
+ }}
+ taskId={activeOutputTaskId}
+ onUserInput={handleUserInput}
+ pendingQuestionIds={pendingQuestionIds}
+ onAnswerQuestion={handleAnswerQuestion}
+ />
+ </div>
</div>
)}
</div>