feat(HAI-001): make task title optional and description required

- Make Task.title optional and description required in types and TaskCreateInput
- Update CLI task create to prompt for description instead of title, with updated help text
- Update REST API and dashboard CreateTaskModal to require description instead of title
- Handle optional/missing title across TaskCard, TaskDetailModal, and engine components
- Add description validation in TaskStore and update generateSpecifiedPrompt
This commit is contained in:
Dustin Byrne
2026-03-25 19:16:20 -04:00
11 changed files with 62 additions and 49 deletions

View File

@@ -12,10 +12,10 @@ export function CreateTaskModal({ onClose, onCreateTask, addToast }: CreateTaskM
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [deps, setDeps] = useState("");
const titleRef = useRef<HTMLInputElement>(null);
const descRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
setTimeout(() => titleRef.current?.focus(), 100);
setTimeout(() => descRef.current?.focus(), 100);
}, []);
useEffect(() => {
@@ -36,9 +36,9 @@ export function CreateTaskModal({ onClose, onCreateTask, addToast }: CreateTaskM
const handleSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
const trimmedTitle = title.trim();
if (!trimmedTitle) return;
if (!description.trim()) return;
const trimmedTitle = title.trim();
const dependencies = deps
.split(",")
.map((s) => s.trim())
@@ -46,8 +46,8 @@ export function CreateTaskModal({ onClose, onCreateTask, addToast }: CreateTaskM
try {
const task = await onCreateTask({
title: trimmedTitle,
description: description.trim() || undefined,
description: description.trim(),
title: trimmedTitle || undefined,
dependencies: dependencies.length ? dependencies : undefined,
});
addToast(`Created ${task.id}`, "success");
@@ -70,27 +70,27 @@ export function CreateTaskModal({ onClose, onCreateTask, addToast }: CreateTaskM
</div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="task-title">Title</label>
<input
ref={titleRef}
type="text"
id="task-title"
placeholder="What needs to be done?"
<label htmlFor="task-desc">Description</label>
<textarea
ref={descRef}
id="task-desc"
rows={4}
placeholder="What needs to be done? Add context, requirements, or rough notes..."
required
value={title}
onChange={(e) => setTitle(e.target.value)}
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="task-desc">
Description <span className="optional">(optional)</span>
<label htmlFor="task-title">
Title <span className="optional">(optional)</span>
</label>
<textarea
id="task-desc"
rows={4}
placeholder="Add context, requirements, or rough notes..."
value={description}
onChange={(e) => setDescription(e.target.value)}
<input
type="text"
id="task-title"
placeholder="Short summary (auto-generated if empty)"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<div className="form-group">

View File

@@ -44,7 +44,9 @@ export function TaskCard({ task, queued, onOpenDetail, addToast }: TaskCardProps
onClick={handleClick}
>
<span className="card-id">{task.id}</span>
<div className="card-title">{task.title}</div>
<div className="card-title">
{task.title || (task.description ? task.description.slice(0, 60) + (task.description.length > 60 ? "…" : "") : task.id)}
</div>
{((task.dependencies && task.dependencies.length > 0) || queued) && (
<div className="card-meta">
{task.dependencies && task.dependencies.length > 0 && (

View File

@@ -90,7 +90,7 @@ export function TaskDetailModal({
</button>
</div>
<div className="detail-body">
<h2 className="detail-title">{task.title}</h2>
<h2 className="detail-title">{task.title || task.id}</h2>
<div className="detail-meta">
Created {new Date(task.createdAt).toLocaleDateString()} · Updated{" "}
{new Date(task.updatedAt).toLocaleDateString()}

View File

@@ -25,8 +25,8 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
router.post("/tasks", async (req, res) => {
try {
const { title, description, column, dependencies } = req.body;
if (!title || typeof title !== "string") {
res.status(400).json({ error: "title is required" });
if (!description || typeof description !== "string") {
res.status(400).json({ error: "description is required" });
return;
}
const task = await store.createTask({