From 64967c19a0a0c2fb16b1e3620a6fd9c75cfd3b73 Mon Sep 17 00:00:00 2001 From: Marcus Rehbock Date: Fri, 7 Aug 2026 23:40:31 -0700 Subject: [PATCH] API: don't null-out empty notes/title (NOT NULL columns); constraint violations return 400 so offline clients drop bad ops Co-Authored-By: Claude Fable 5 --- server/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/index.ts b/server/index.ts index e0318b1..97ce54c 100644 --- a/server/index.ts +++ b/server/index.ts @@ -31,9 +31,12 @@ function err(message: string, status: number) { const TASK_FIELDS = ["title", "notes", "status", "project_id", "context", "waiting_for", "due_date", "defer_date", "priority", "estimate_min", "sort_order"]; const PROJECT_FIELDS = ["name", "status", "notes", "sort_order"]; +// Only these may be cleared to NULL; notes/title are NOT NULL and keep "". +const NULLABLE = new Set(["project_id", "context", "waiting_for", "due_date", "defer_date", "estimate_min"]); + function pick(body: Record, fields: string[]) { const out: Record = {}; - for (const f of fields) if (f in body) out[f] = body[f] === "" ? null : body[f]; + for (const f of fields) if (f in body) out[f] = body[f] === "" && NULLABLE.has(f) ? null : body[f]; return out; } @@ -166,6 +169,10 @@ Bun.serve({ fetch: (req) => handle(req).catch((e) => { console.error(e); + // Constraint violations are bad input, not server faults — 400 so + // offline clients drop the op instead of retrying it forever. + const errno = (e as { errno?: string }).errno ?? ""; + if (errno.startsWith("23")) return err(`constraint violation: ${(e as Error).message}`, 400); return err("internal error", 500); }), });