summaryrefslogtreecommitdiff
path: root/makima/frontend
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-22 13:16:01 +0000
committersoryu <soryu@soryu.co>2026-01-22 13:16:01 +0000
commitc5cd9fe0515f024a6f442e6b7eca614a38aa6deb (patch)
tree3945da69ce346798b413d7063485aebfd5be3d11 /makima/frontend
parent1b1b737006f9505b2a188a669c5a37671658ce3f (diff)
downloadsoryu-c5cd9fe0515f024a6f442e6b7eca614a38aa6deb.tar.gz
soryu-c5cd9fe0515f024a6f442e6b7eca614a38aa6deb.zip
Add dismiss functionality for completed standalone tasksmakima/task-task-5dde682c-5dde682c
## Changes ### Backend - Add 'hidden' field to Task model (models.rs) - Add database migration for hidden column (20250122000000_add_task_hidden.sql) - Update task listing queries to include hidden field and filter out hidden tasks - Update update_task_for_owner to handle hidden field ### Frontend - Add hidden field to TaskSummary interface (api.ts) - Add dismissTask API function (api.ts) - Add hideTask function to useTasks hook - Add Dismiss button to TaskList for completed standalone tasks - Wire up onDismiss handler in mesh.tsx route ## Behavior - Completed standalone tasks (tasks without a contract) show a "Dismiss" button - Dismissing a task sets hidden=true and removes it from the task list - Hidden tasks are filtered out by default in all task listing queries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend')
-rw-r--r--makima/frontend/package-lock.json14
-rw-r--r--makima/frontend/src/components/mesh/TaskList.tsx38
-rw-r--r--makima/frontend/src/hooks/useTasks.ts17
-rw-r--r--makima/frontend/src/lib/api.ts16
-rw-r--r--makima/frontend/src/routes/mesh.tsx10
5 files changed, 82 insertions, 13 deletions
diff --git a/makima/frontend/package-lock.json b/makima/frontend/package-lock.json
index 88297c2..cc842b1 100644
--- a/makima/frontend/package-lock.json
+++ b/makima/frontend/package-lock.json
@@ -54,6 +54,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -960,6 +961,7 @@
"resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.11.0.tgz",
"integrity": "sha512-g1ou5Zw3r4mCU0L+EXH4vRtAiyt8qz1JOvL1k+PW4rZ4+71h5nBy/fLgD7cg5BnzQZmjRO1PzCgpF5BIrlKYxQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@babel/core": "^7.27.7",
"@babel/generator": "^7.27.5",
@@ -1857,6 +1859,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"devOptional": true,
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1973,6 +1976,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2793,6 +2797,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -2864,6 +2869,7 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2872,6 +2878,7 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -2889,6 +2896,7 @@
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -2920,6 +2928,7 @@
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
+ "peer": true,
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@@ -2979,7 +2988,8 @@
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
- "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -3116,6 +3126,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3207,6 +3218,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
diff --git a/makima/frontend/src/components/mesh/TaskList.tsx b/makima/frontend/src/components/mesh/TaskList.tsx
index 80077b6..a38fe07 100644
--- a/makima/frontend/src/components/mesh/TaskList.tsx
+++ b/makima/frontend/src/components/mesh/TaskList.tsx
@@ -6,6 +6,7 @@ interface TaskListProps {
loading: boolean;
onSelect: (id: string) => void;
onDelete: (id: string) => void;
+ onDismiss: (id: string) => void;
onCreate: () => void;
}
@@ -88,6 +89,7 @@ export function TaskList({
loading,
onSelect,
onDelete,
+ onDismiss,
onCreate,
}: TaskListProps) {
// Filter state - default to 'active' to show only active contracts
@@ -291,17 +293,31 @@ export function TaskList({
</div>
</button>
{/* Supervisor tasks cannot be deleted directly - they are deleted with the contract */}
- {!task.isSupervisor && (
- <button
- onClick={(e) => {
- e.stopPropagation();
- onDelete(task.id);
- }}
- className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
- >
- Delete
- </button>
- )}
+ <div className="flex gap-2">
+ {/* Show dismiss button for completed standalone tasks (tasks without a contract) */}
+ {!task.contractId && (task.status === "done" || task.status === "failed" || task.status === "merged") && (
+ <button
+ onClick={(e) => {
+ e.stopPropagation();
+ onDismiss(task.id);
+ }}
+ className="px-2 py-1 font-mono text-[10px] text-[#8b949e] hover:bg-[rgba(117,170,252,0.1)] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase"
+ >
+ Dismiss
+ </button>
+ )}
+ {!task.isSupervisor && (
+ <button
+ onClick={(e) => {
+ e.stopPropagation();
+ onDelete(task.id);
+ }}
+ className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
+ >
+ Delete
+ </button>
+ )}
+ </div>
</div>
</div>
))}
diff --git a/makima/frontend/src/hooks/useTasks.ts b/makima/frontend/src/hooks/useTasks.ts
index 6e6c992..4667c4c 100644
--- a/makima/frontend/src/hooks/useTasks.ts
+++ b/makima/frontend/src/hooks/useTasks.ts
@@ -5,6 +5,7 @@ import {
createTask,
updateTask,
deleteTask,
+ dismissTask,
VersionConflictError,
type TaskSummary,
type TaskWithSubtasks,
@@ -110,6 +111,21 @@ export function useTasks() {
[fetchTasks]
);
+ const hideTask = useCallback(
+ async (id: string): Promise<boolean> => {
+ setError(null);
+ try {
+ await dismissTask(id);
+ await fetchTasks(); // Refresh list
+ return true;
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to dismiss task");
+ return false;
+ }
+ },
+ [fetchTasks]
+ );
+
// Initial fetch
useEffect(() => {
fetchTasks();
@@ -126,5 +142,6 @@ export function useTasks() {
saveTask,
editTask,
removeTask,
+ hideTask,
};
}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 86ff06c..b42a19f 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -543,6 +543,8 @@ export interface TaskSummary {
subtaskCount: number;
/** Whether this is a supervisor task (contract orchestrator) */
isSupervisor: boolean;
+ /** Whether this task is hidden from the UI (user dismissed it) */
+ hidden: boolean;
version: number;
createdAt: string;
updatedAt: string;
@@ -639,6 +641,8 @@ export interface UpdateTaskRequest {
targetRepoPath?: string;
/** Action on completion: "none", "branch", "merge", "pr" */
completionAction?: CompletionAction;
+ /** Whether this task is hidden from the UI (user dismissed it) */
+ hidden?: boolean;
version?: number;
}
@@ -2631,3 +2635,15 @@ export function getSupervisorStatus(
canResume,
};
}
+
+// =============================================================================
+// Task Dismiss (Hide) Functions
+// =============================================================================
+
+/**
+ * Dismiss (hide) a completed standalone task from the UI.
+ * This marks the task as hidden so it won't appear in the task list.
+ */
+export async function dismissTask(taskId: string): Promise<Task> {
+ return updateTask(taskId, { hidden: true });
+}
diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx
index 453bdff..d3ca84e 100644
--- a/makima/frontend/src/routes/mesh.tsx
+++ b/makima/frontend/src/routes/mesh.tsx
@@ -81,7 +81,7 @@ export default function MeshPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
- const { tasks, loading, error, conflict, clearConflict, fetchTask, fetchTasks, editTask, removeTask, saveTask } = useTasks();
+ const { tasks, loading, error, conflict, clearConflict, fetchTask, fetchTasks, editTask, removeTask, hideTask, saveTask } = useTasks();
const { pendingQuestions, submitAnswer } = useSupervisorQuestions();
// Memoize pending question IDs for efficient lookup
@@ -373,6 +373,13 @@ export default function MeshPage() {
[removeTask, id, taskDetail, navigate]
);
+ const handleDismiss = useCallback(
+ async (taskId: string) => {
+ await hideTask(taskId);
+ },
+ [hideTask]
+ );
+
const handleStart = useCallback(
async (taskId: string) => {
try {
@@ -830,6 +837,7 @@ export default function MeshPage() {
loading={loading || creating}
onSelect={handleSelectTask}
onDelete={handleDelete}
+ onDismiss={handleDismiss}
onCreate={handleCreate}
/>
</div>