From 16f5027ac8728faca148d05c5aa2c20219cf67e5 Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 22 Mar 2026 16:31:18 +0000 Subject: WIP: heartbeat checkpoint --- .../src/components/directives/TaskSlideOutPanel.tsx | 10 ++++++---- makima/src/db/models.rs | 3 +++ makima/src/server/handlers/mesh_supervisor.rs | 15 +++++++++++++-- makima/src/server/state.rs | 12 +++++++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx b/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx index 176728c..378ef9b 100644 --- a/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx +++ b/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx @@ -254,10 +254,12 @@ export function TaskSlideOutPanel({ )} - {/* Worktree Changes section (~40% height) */} -
- {taskId && } -
+ {/* Worktree Changes section (~40% height) */} +
+ {taskId && } +
+ + )} diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 4537276..88cd6c6 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -2003,6 +2003,9 @@ pub struct PendingQuestion { pub question_type: String, /// When the question was asked pub asked_at: DateTime, + /// Optional image attachments (e.g., screenshots for visual feedback) + #[serde(default)] + pub images: Vec, } fn default_question_type() -> String { diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs index ebde52b..75e9401 100644 --- a/makima/src/server/handlers/mesh_supervisor.rs +++ b/makima/src/server/handlers/mesh_supervisor.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use uuid::Uuid; -use crate::db::models::{CreateOrderRequest, CreateTaskRequest, PendingQuestion, Task, TaskSummary, UpdateTaskRequest}; +use crate::db::models::{CreateOrderRequest, CreateTaskRequest, PendingQuestion, QuestionImage, Task, TaskSummary, UpdateTaskRequest}; use crate::db::repository; use sqlx::PgPool; use crate::server::auth::Authenticated; @@ -84,6 +84,9 @@ pub struct AskQuestionRequest { /// Question type: general, phase_confirmation, or contract_complete #[serde(default = "default_question_type")] pub question_type: String, + /// Optional image attachments (e.g., screenshots for visual feedback) + #[serde(default)] + pub images: Vec, } fn default_question_type() -> String { @@ -146,6 +149,9 @@ pub struct PendingQuestionSummary { /// Question type: general, phase_confirmation, or contract_complete #[serde(default)] pub question_type: String, + /// Optional image attachments (e.g., screenshots for visual feedback) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub images: Vec, } /// Request to create a checkpoint. @@ -1744,6 +1750,7 @@ pub async fn ask_question( request.context.clone(), request.multi_select, request.question_type.clone(), + request.images.clone(), ); // Save state: question asked is a key save point (Task 3.3) @@ -1756,6 +1763,7 @@ pub async fn ask_question( context: request.context.clone(), question_type: request.question_type.clone(), asked_at: chrono::Utc::now(), + images: request.images.clone(), }; save_state_on_question_asked(pool, cid, pending_question).await; } @@ -2047,6 +2055,7 @@ pub async fn list_pending_questions( created_at: q.created_at, multi_select: q.multi_select, question_type: q.question_type, + images: q.images, }) .collect(); @@ -3015,15 +3024,17 @@ pub async fn redeliver_pending_questions( ) { for question in questions { // Add to in-memory question state - state.add_supervisor_question( + state.add_supervisor_question_with_directive( supervisor_id, contract_id, + None, owner_id, question.question.clone(), question.choices.clone(), question.context.clone(), false, // Assume single select for restored questions question.question_type.clone(), + question.images.clone(), ); // Broadcast to WebSocket clients diff --git a/makima/src/server/state.rs b/makima/src/server/state.rs index 1f7b264..951c9b6 100644 --- a/makima/src/server/state.rs +++ b/makima/src/server/state.rs @@ -6,6 +6,7 @@ use sqlx::PgPool; use tokio::sync::{broadcast, mpsc, oneshot, Mutex, OnceCell}; use uuid::Uuid; +use crate::db::models::QuestionImage; use crate::listen::{DiarizationConfig, ParakeetEOU, ParakeetTDT, Sortformer}; use crate::server::auth::{AuthConfig, JwtVerifier}; use crate::tts::TtsEngine; @@ -165,6 +166,9 @@ pub struct SupervisorQuestionNotification { /// Whether multiple choices can be selected #[serde(default)] pub multi_select: bool, + /// Optional image attachments (e.g., screenshots for visual feedback) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub images: Vec, } /// Stored supervisor question for persistence @@ -184,6 +188,8 @@ pub struct PendingSupervisorQuestion { pub multi_select: bool, /// Question type: general, phase_confirmation, or contract_complete pub question_type: String, + /// Optional image attachments (e.g., screenshots for visual feedback) + pub images: Vec, } /// Response to a supervisor question @@ -897,7 +903,7 @@ impl AppState { ) -> Uuid { self.add_supervisor_question_with_directive( task_id, contract_id, None, owner_id, - question, choices, context, multi_select, question_type, + question, choices, context, multi_select, question_type, vec![], ) } @@ -913,6 +919,7 @@ impl AppState { context: Option, multi_select: bool, question_type: String, + images: Vec, ) -> Uuid { let question_id = Uuid::new_v4(); let now = chrono::Utc::now(); @@ -932,6 +939,7 @@ impl AppState { created_at: now, multi_select, question_type: question_type.clone(), + images: images.clone(), }, ); @@ -948,6 +956,7 @@ impl AppState { pending: true, created_at: now, multi_select, + images, }); tracing::info!( @@ -1015,6 +1024,7 @@ impl AppState { pending: false, created_at: question.1.created_at, multi_select: question.1.multi_select, + images: question.1.images, }); tracing::info!( -- cgit v1.2.3 From 24469d2d6d58ed683dac5761669ab29f1730f56f Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 22 Mar 2026 16:36:18 +0000 Subject: WIP: heartbeat checkpoint --- .../src/components/directives/TaskSlideOutPanel.tsx | 2 ++ .../frontend/src/components/mesh/WorktreeFilesPanel.tsx | 2 +- makima/frontend/src/lib/api.ts | 7 +++++++ makima/frontend/tsconfig.tsbuildinfo | 2 +- makima/src/orchestration/directive.rs | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx b/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx index 378ef9b..f803f90 100644 --- a/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx +++ b/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx @@ -25,6 +25,8 @@ export function TaskSlideOutPanel({ const [showDiff, setShowDiff] = useState(false); const [diffContent, setDiffContent] = useState(""); const [diffLoading, setDiffLoading] = useState(false); + const [selectedFileDiff, setSelectedFileDiff] = useState(null); + const [selectedFilePath, setSelectedFilePath] = useState(null); // Escape key handler useEffect(() => { diff --git a/makima/frontend/src/components/mesh/WorktreeFilesPanel.tsx b/makima/frontend/src/components/mesh/WorktreeFilesPanel.tsx index bb3361d..500fb6a 100644 --- a/makima/frontend/src/components/mesh/WorktreeFilesPanel.tsx +++ b/makima/frontend/src/components/mesh/WorktreeFilesPanel.tsx @@ -154,7 +154,7 @@ export function WorktreeFilesPanel({ taskId, onFileClick }: WorktreeFilesPanelPr return (
onFileClick?.(file.path)} > {/* Status badge */} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index dd981ed..48e0faa 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3089,6 +3089,13 @@ export async function getTaskDiff(taskId: string): Promise<{ taskId: string; suc return res.json(); } +/** Get the diff for a specific file in a task's worktree (falls back to full task diff filtered client-side) */ +export async function getWorktreeDiff(taskId: string, _filePath?: string): Promise<{ diff: string }> { + const result = await getTaskDiff(taskId); + // Return the full diff - per-file filtering can be done client-side if needed + return { diff: result.diff || "" }; +} + /** Commit changes in a task's worktree */ export async function commitWorktree(taskId: string, message?: string): Promise<{ taskId: string; success: boolean; commitSha: string | null; error: string | null }> { const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/worktree-commit`, { diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 031f180..7a057d5 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/questionimages.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/doglist.tsx","./src/components/directives/directivecontextmenu.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/orchestratorstepnode.tsx","./src/components/directives/stepnode.tsx","./src/components/directives/taskslideoutpanel.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/ordercontextmenu.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usedogs.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/daemons.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/questionimages.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/doglist.tsx","./src/components/directives/directivecontextmenu.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/orchestratorstepnode.tsx","./src/components/directives/stepnode.tsx","./src/components/directives/taskslideoutpanel.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/ordercontextmenu.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usedogs.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/daemons.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index 1e025c8..78b30ce 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -211,7 +211,11 @@ impl DirectiveOrchestrator { When done, the system will automatically mark this step as completed.\n\ If you cannot complete the task, report the failure clearly.\n\n\ If you need clarification or encounter a decision that requires user input, you can ask:\n\ - \x20 makima directive ask \"Your question\" --phaseguard{manual_mode_appendix}", + \x20 makima directive ask \"Your question\" --phaseguard\n\n\ + VISUAL FEEDBACK: If this step involves frontend/UI changes, you can take screenshots and ask for user review:\n\ + \x20 npx playwright screenshot --browser chromium screenshot.png\n\ + \x20 makima directive ask \"Does this look correct?\" --images \"screenshot.png\" --phaseguard\n\ + Node.js and Playwright with Chromium are pre-installed in the environment.{manual_mode_appendix}", directive_title = step.directive_title, step_name = step.step_name, description = step.step_description.as_deref().unwrap_or("(none)"), @@ -1652,6 +1656,16 @@ When to create orders: Do NOT create orders for: - Work that should be a step in the current plan - Tasks that are part of the current goal + +VISUAL FEEDBACK FOR FRONTEND CHANGES: +Tasks can take screenshots of web applications and ask users for visual feedback: +1. Start the dev server in the worktree background +2. Use `npx playwright screenshot --browser chromium screenshot.png` to capture the UI +3. Use `makima directive ask "Review the UI" --images "screenshot.png" --phaseguard` to show the user +4. Node.js and Playwright with Chromium are pre-installed in the daemon environment + +For steps involving frontend/UI changes, include instructions in the taskPlan to take screenshots and ask +for visual confirmation before completing the step. This ensures the user can see and approve visual changes. "#, title = directive.title, goal = directive.goal, -- cgit v1.2.3