Q149: editing a task can change everything creating it can

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 01:04:26 -04:00
1 parent 766d4ab8cd
commit 7ab46bb2de
14 files changed
+150 -16

No files matched your search

+2 -1
View File
@@ -41,7 +41,8 @@ export const organizationContracts = {
}) satisfies z.ZodType<C.CreateTaskInput>,
updateTask: z.strictObject({
orgId: id, taskId: id, title: z.string(), description: z.string(), assigneeAgentId: nullableId, risk: z.enum(RISK_LEVEL),
priority: z.enum(TASK_PRIORITY), parentTaskId: nullableId,
priority: z.enum(TASK_PRIORITY), parentTaskId: nullableId, projectId: nullableId, goalId: nullableId, workspaceId: nullableId,
requiresFilesystem: z.boolean(), writingMode: z.enum(WRITING_MODE),
}) satisfies z.ZodType<C.UpdateTaskInput>,
deleteTask: z.strictObject({ orgId: id, taskId: id }) satisfies z.ZodType<C.DeleteTaskInput>,
updateTaskStatus: z.strictObject({ orgId: id, taskId: id, status: z.enum(TASK_STATUS) }) satisfies z.ZodType<C.UpdateTaskStatusInput>,
@@ -466,6 +466,9 @@ export class OrganizationService {
}
updateTask(input: UpdateTaskInput): void {
this.verifyOptionalOrgEntity("goals", input.goalId, input.orgId, "Goal");
this.verifyOptionalOrgEntity("workspaces", input.workspaceId, input.orgId, "Workspace");
this.verifyProject(input.projectId, input.goalId, input.orgId);
this.tasks.update(input);
}
+6 -2
View File
@@ -49,10 +49,14 @@ export class TaskMutations {
this.applyStatus(input.orgId, before, TASK_STATUS.IN_PROGRESS, { type: "system" }, null);
}
const result = this.db.prepare(`
update tasks set title = ?, description = ?, assignee_agent_id = ?, risk = ?, priority = ?, parent_task_id = ?, updated_at = ?,
update tasks set title = ?, description = ?, assignee_agent_id = ?, risk = ?, priority = ?, parent_task_id = ?,
project_id = ?, goal_id = ?, workspace_id = ?, requires_filesystem = ?, writing_mode = ?, updated_at = ?,
stalled_reason = null, enqueue_error = null, run_budget_floor = (select coalesce(max(rowid), 0) from task_runs)
where id = ? and org_id = ?
`).run(input.title.trim(), input.description.trim(), input.assigneeAgentId, input.risk, input.priority, input.parentTaskId, now, input.taskId, input.orgId);
`).run(
input.title.trim(), input.description.trim(), input.assigneeAgentId, input.risk, input.priority, input.parentTaskId,
input.projectId, input.goalId, input.workspaceId, input.requiresFilesystem ? 1 : 0, input.writingMode, now, input.taskId, input.orgId,
);
if (result.changes === 0) throw new Error("Task was not found.");
this.requestApproval(input.orgId, input.taskId);
this.audit.record({
+1 -6
View File
@@ -5,6 +5,7 @@ import type { AppSnapshot, RiskLevel, TaskPriority, WritingMode } from "../../..
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, WRITING_MODE } from "../../../shared/domain";
import type { SnapshotActions } from "../app/useSnapshot";
import { t } from "../i18n/i18n";
import { WRITING_OPTIONS } from "./work/task-helpers";
const RISK_OPTIONS: ReadonlyArray<{ value: RiskLevel; label: string }> = [
{ value: RISK_LEVEL.LOW, label: t("Low") },
@@ -18,12 +19,6 @@ const PRIORITY_OPTIONS: ReadonlyArray<{ value: TaskPriority; label: string }> =
{ value: TASK_PRIORITY.HIGH, label: t("High") },
];
const WRITING_OPTIONS: ReadonlyArray<{ value: WritingMode; label: string }> = [
{ value: WRITING_MODE.CONVERSATIONAL, label: t("Conversational (house style)") },
{ value: WRITING_MODE.TECHNICAL, label: t("Technical spec") },
{ value: WRITING_MODE.STRUCTURED, label: t("Structured output") },
];
function value(form: FormData, key: string): string {
return String(form.get(key) ?? "").trim();
}
@@ -12,9 +12,9 @@ import type {
TaskRunRecord,
WorkspaceRecord,
} from "../../../../shared/domain";
import { TASK_STATUS } from "../../../../shared/domain";
import { TASK_STATUS, type WritingMode } from "../../../../shared/domain";
import { auditEventLabel, formatRelative } from "../types";
import { descendantIds, nextStatus, prettyRunState, prettyStatus, providerFor, runBlocker, runsForTask, runTone } from "./task-helpers";
import { WRITING_OPTIONS, descendantIds, nextStatus, prettyRunState, prettyStatus, providerFor, runBlocker, runsForTask, runTone } from "./task-helpers";
import type { SnapshotActions } from "../../app/useSnapshot";
import type { Focus, PageProps } from "../types";
import { t } from "../../i18n/i18n";
@@ -95,6 +95,11 @@ export function TaskDetailDialog({
risk: (val("risk") || "low") as "low" | "medium" | "high",
priority: (val("priority") || "normal") as "low" | "normal" | "high",
parentTaskId: val("parentTaskId") || null,
projectId: val("projectId") || null,
goalId: val("goalId") || null,
workspaceId: val("workspaceId") || null,
requiresFilesystem: form.get("requiresFilesystem") === "on",
writingMode: val("writingMode") as WritingMode,
})).then((r) => { if (r) setEditing(false); }, (error: unknown) => setEditError(error instanceof Error ? error.message : String(error)));
};
@@ -149,7 +154,19 @@ export function TaskDetailDialog({
{advanceTo && <Button tone="primary" onClick={() => onAdvance(task)}><Icon name="check" size={16} /> {prettyStatus(advanceTo)}</Button>}
</div>
{editing && <EditOverlay task={task} agents={agents} allTasks={allTasks} error={editError} onSubmit={submitEdit} onCancel={() => { setEditing(false); setEditError(null); }} />}
{editing && (
<EditOverlay
task={task}
agents={agents}
allTasks={allTasks}
projects={projects}
goals={goals}
workspaces={workspaces}
error={editError}
onSubmit={submitEdit}
onCancel={() => { setEditing(false); setEditError(null); }}
/>
)}
</div>
</div>
);
@@ -251,8 +268,9 @@ function ActivityTab({ audit, approvals, onOpenApproval }: { audit: AuditEventRe
);
}
function EditOverlay({ task, agents, allTasks, error, onSubmit, onCancel }: {
task: TaskRecord; agents: AgentRecord[]; allTasks: TaskRecord[]; error: string | null;
function EditOverlay({ task, agents, allTasks, projects, goals, workspaces, error, onSubmit, onCancel }: {
task: TaskRecord; agents: AgentRecord[]; allTasks: TaskRecord[];
projects: ProjectRecord[]; goals: GoalRecord[]; workspaces: WorkspaceRecord[]; error: string | null;
onSubmit: (e: FormEvent<HTMLFormElement>) => void; onCancel: () => void;
}) {
const beneath = descendantIds(task.id, allTasks);
@@ -287,6 +305,32 @@ function EditOverlay({ task, agents, allTasks, error, onSubmit, onCancel }: {
{otherTasks.map((t) => <option key={t.id} value={t.id}>{t.title}</option>)}
</select>
</Field>
<Field label={t("Project")}>
<select className="input" name="projectId" defaultValue={task.projectId ?? ""}>
<option value="">{t("None")}</option>
{projects.map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}
</select>
</Field>
<Field label={t("Goal")}>
<select className="input" name="goalId" defaultValue={task.goalId ?? ""}>
<option value="">{t("None")}</option>
{goals.map((goal) => <option key={goal.id} value={goal.id}>{goal.title}</option>)}
</select>
</Field>
<Field label={t("Workspace")}>
<select className="input" name="workspaceId" defaultValue={task.workspaceId ?? ""}>
<option value="">{t("None")}</option>
{workspaces.map((workspace) => <option key={workspace.id} value={workspace.id}>{workspace.name}</option>)}
</select>
</Field>
<Field label={t("Writing")}>
<select className="input" name="writingMode" defaultValue={task.writingMode}>
{WRITING_OPTIONS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
</select>
</Field>
<label className="checkbox-row">
<input type="checkbox" name="requiresFilesystem" defaultChecked={task.requiresFilesystem} /> {t("Requires workspace access")}
</label>
{error && <div className="form-error" role="alert">{error}</div>}
<div className="modal-actions">
<Button onClick={onCancel}>{t("Cancel")}</Button>
@@ -1,13 +1,23 @@
import {
TASK_STATUS,
WRITING_MODE,
type AgentRecord,
type ProviderRecord,
type TaskRecord,
type TaskRunRecord,
type TaskStatus,
type WritingMode,
} from "../../../../shared/domain";
import { t } from "../../i18n/i18n";
/** The writing modes a task can ask for, as the create and edit forms offer them. */
export const WRITING_OPTIONS: ReadonlyArray<{ value: WritingMode; label: string }> = [
{ value: WRITING_MODE.CONVERSATIONAL, label: t("Conversational (house style)") },
{ value: WRITING_MODE.TECHNICAL, label: t("Technical spec") },
{ value: WRITING_MODE.STRUCTURED, label: t("Structured output") },
];
export function nextStatus(status: TaskStatus): TaskStatus | null {
if (status === TASK_STATUS.BACKLOG) return TASK_STATUS.IN_PROGRESS;
if (status === TASK_STATUS.IN_PROGRESS) return TASK_STATUS.REVIEW;
+5
View File
@@ -116,6 +116,11 @@ export interface UpdateTaskInput {
risk: RiskLevel;
priority: TaskPriority;
parentTaskId: string | null;
projectId: string | null;
goalId: string | null;
workspaceId: string | null;
requiresFilesystem: boolean;
writingMode: WritingMode;
}
export interface DeleteTaskInput {