Initial scaffold: Hammer Queue task dashboard

- Backend: Elysia + Bun + Drizzle ORM + PostgreSQL
- Frontend: React + Vite + TypeScript + Tailwind CSS
- Task CRUD API with bearer token auth for writes
- Public read-only dashboard with auto-refresh
- Task states: active, queued, blocked, completed, cancelled
- Reorder support for queue management
- Progress notes per task
- Docker Compose for local dev and Dokploy deployment
This commit is contained in:
2026-01-28 22:55:16 +00:00
commit 0a8d5486bb
36 changed files with 2210 additions and 0 deletions

62
frontend/src/lib/api.ts Normal file
View File

@@ -0,0 +1,62 @@
import type { Task } from "./types";
const BASE = "/api/tasks";
export async function fetchTasks(): Promise<Task[]> {
const res = await fetch(BASE);
if (!res.ok) throw new Error("Failed to fetch tasks");
return res.json();
}
export async function updateTask(
id: string,
updates: Record<string, any>,
token: string
): Promise<Task> {
const res = await fetch(`${BASE}/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(updates),
});
if (!res.ok) throw new Error("Failed to update task");
return res.json();
}
export async function reorderTasks(ids: string[], token: string): Promise<void> {
const res = await fetch(`${BASE}/reorder`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ ids }),
});
if (!res.ok) throw new Error("Failed to reorder tasks");
}
export async function createTask(
task: { title: string; description?: string; source?: string; priority?: string; status?: string },
token: string
): Promise<Task> {
const res = await fetch(BASE, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(task),
});
if (!res.ok) throw new Error("Failed to create task");
return res.json();
}
export async function deleteTask(id: string, token: string): Promise<void> {
const res = await fetch(`${BASE}/${id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error("Failed to delete task");
}

22
frontend/src/lib/types.ts Normal file
View File

@@ -0,0 +1,22 @@
export type TaskStatus = "active" | "queued" | "blocked" | "completed" | "cancelled";
export type TaskPriority = "critical" | "high" | "medium" | "low";
export type TaskSource = "donovan" | "david" | "hammer" | "heartbeat" | "cron" | "other";
export interface ProgressNote {
timestamp: string;
note: string;
}
export interface Task {
id: string;
title: string;
description: string | null;
source: TaskSource;
status: TaskStatus;
priority: TaskPriority;
position: number;
progressNotes: ProgressNote[];
createdAt: string;
updatedAt: string;
completedAt: string | null;
}