Public: anyone can read this; the crew can change it.

propose refuses an option that is not a string, rather than coercing it #41

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`propose` now refuses an `options` entry that is not a string instead of coercing it: `if (input.options.some((label) => typeof label !== "string")) return fail("invalid", "Every option is a line of text.")`, placed straight after the `Array.isArray` guard so it is said before the count, blank, length and duplicate rules — the first thing wrong is the thing named. The map below it drops the `?? ""…

shipped production is serving it deploy 2adbe7d

opened this

propose now refuses an options entry that is not a string instead of coercing it: if (input.options.some((label) => typeof label !== "string")) return fail("invalid", "Every option is a line of text."), placed straight after the Array.isArray guard so it is said before the count, blank, length and duplicate rules — the first thing wrong is the thing named. The map below it drops the ?? "" that made the coercion possible.

src/actions.test.ts grows a case in the existing bad-options table ([{}, ["x"]], which used to open a proposal labelled "[object Object]" and "x") and a test that pins the message and its order against [{}], [null, null] and [1, 1] — single-entry and duplicate inputs that would otherwise be answered by the count or duplicate rule. Both fail without the check; bun test is green at 77 tests. Nothing changed at the doors: the page's textarea is still split into lines by the route, and the MCP schema already declared items: { type: "string" }. The README's proposal paragraph now says the array takes text and that a caller sending otherwise is refused.

To try it in the preview, the signed API path answers 422 where it used to answer 201:

curl -X POST "$APP/api/proposals" -H content-type:application/json \
  -d '{"title":"Weird","body":"b","options":[{"a":1},["x"]]}'
# {"error":"invalid","message":"Every option is a line of text."}

while "options":["Yes","No"] still opens a proposal. Left alone deliberately: addOption (the counter-proposal path) takes a single optionLabel field that the same reasoning would touch, but it is a different action with its own tests and is not what #17 describes.

🤖 Generated with Claude Code

Closes #17

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs