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:
@@ -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">
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user