Skip to content

fix(cli): variant date flags — variants add could never succeed - #9

Merged
akoso merged 1 commit into
mainfrom
fix/variant-dates
Aug 1, 2026
Merged

fix(cli): variant date flags — variants add could never succeed#9
akoso merged 1 commit into
mainfrom
fix/variant-dates

Conversation

@akoso

@akoso akoso commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

What was broken

mna variants add could not succeed against production. A variant is what carries the trip's
dates, and the API rejects a create without them — but the command exposed no way to supply
them. Every invocation ended as:

$ mna variants add <tripId> --name "Beach option"
✖ HTTP 400

The 400 body is {"statusCode":400,"timestamp":…,"path":…} with no message, so the CLI had
nothing to print and the user had nothing to act on. variants edit had the same gap in the
other direction: no way to set dates at all, and no guard against the shapes the update path
rejects (dates: null, half-filled { startDate }).

Confirmed directly against production before the fix:

body result
POST …/variants {"name":"x"} 400 (no message)
POST …/variants {"name":"x","dates":{"startDate":…}} 400
POST …/variants {"name":"x","dates":null} 400
PATCH …/variants/<id> {"dates":null} 400
PATCH …/variants/<id> {"dates":{"startDate":…}} 400
PATCH …/variants/<id> {"name":"x"} (no dates) 200 — dates left alone

The fix

Date flags on variants add and variants edit, following the destinations --start-date / --end-date precedent already on main and reusing normalizeToIsoDateTime (accepts
YYYY-MM-DD or a full ISO date-time). The v1 contract's two shapes map to two flag groups:

  • exact--start-date, --end-date
  • flexible--depart-not-before, --depart-not-after, --return-not-before,
    --return-not-after, --min-nights, --max-nights

A new src/commands/variants/dates.ts owns the flag definitions (spread into both commands) and
the body building, so the two commands cannot drift:

  • variants add uses requireVariantDates and fails fast client-side when no date flag is
    given, naming both shapes — no doomed request.
  • Half-filled sets are rejected locally, naming the missing flag; mixing the two shapes is
    rejected as such. Night counts must be whole numbers.
  • variants edit uses buildVariantDates, which returns undefined when no date flag is
    given — so dates is omitted entirely ("leave alone"), and the command has no path that
    produces dates: null or a partial object.

Production round-trip evidence

Throwaway trip, both shapes, read back, deleted, cleanup confirmed — all through the CLI:

$ mna trips create --name "CLI e2e — variants add dates" --json     → 6a6e508616ebbce80e3fa996

$ mna variants add <trip> --name "Exact window" --start-date 2026-09-01 --end-date 2026-09-08
{ "id": "6a6e508716ebbce80e3fa99c", "name": "Exact window" }

$ mna variants add <trip> --name "Flexible window" \
    --depart-not-before 2026-09-01 --depart-not-after 2026-09-03 \
    --return-not-before 2026-09-10 --return-not-after 2026-09-12 \
    --min-nights 7 --max-nights 10
{ "id": "6a6e508716ebbce80e3fa9a7", "name": "Flexible window" }

$ mna variants edit <trip> <exactVariant> --start-date 2026-09-02 --end-date 2026-09-09   ✓
$ mna variants edit <trip> <exactVariant> --notes "notes only, dates untouched"           ✓

$ mna trips show <trip> --json
Exact window    | notes only, dates untouched | {"startDate":"2026-09-02T00:00:00.000Z","endDate":"2026-09-09T00:00:00.000Z"}
Flexible window | (no notes)                  | {"departLeavingNotBeforeDate":"2026-09-01T00:00:00.000Z",
                                                 "departArrivingNotAfterDate":"2026-09-03T00:00:00.000Z",
                                                 "returnLeavingNotBeforeDate":"2026-09-10T00:00:00.000Z",
                                                 "returnArrivingNotAfterDate":"2026-09-12T00:00:00.000Z",
                                                 "minNights":7,"maxNights":10}

$ mna trips delete <trip> --yes    ✓
$ mna trips show <trip> --json     ✖ HTTP 404      (and gone from `trips list`)

The edit round-trip also shows the "leave alone" path holding: a notes-only edit after the date
edit left startDate/endDate intact.

Client-side guards, no request sent:

✖ A variant cannot be created without dates. Supply exact dates (--start-date, --end-date) or
  flexible dates (--depart-not-before, --depart-not-after, --return-not-before,
  --return-not-after, --min-nights, --max-nights).
✖ Incomplete dates: --start-date, --end-date must be given together. Missing --end-date.
✖ Mixed date shapes: use either exact dates (…) or flexible dates (…), not both.

Event location coordinates — doc correction

skills/mna/references/cli-and-schemas.md documented event location coordinates as
coordinates.latitude/.longitude. CreateEventLocationCoordinatesV1Dto requires lat/lng,
and production agrees — verified on the same throwaway trip via mna events add --from-json:

body location.coordinates result read back
{"lat":38.7075,"lng":-9.1364} 201 {"lat":38.7075,"lng":-9.1364} + formattedAddress persisted
{"latitude":38.7139,"longitude":-9.1334} 500 nothing written
flat address/latitude/longitude (no coordinates) 201 only name survives

So the documented form wasn't a silent drop — it was a server error. Doc corrected to lat/lng
for event locations too. The neighbouring accommodation/transport location paragraph is left
untouched; #8 owns that one.

Prior art

main already carries the date-flag design from 4198ed5 (destinations --start-date/--end-date

  • src/util/dates.ts) — it survived the stacked-PR merge. Nothing was lost, and nothing needed
    resurrecting; this change follows that design rather than inventing a second one.

Gates

bun run codegen (no-op vs the committed snapshot) · bun run typecheck ✓ ·
bun run lint ✓ (227 files, no findings) · bun test ✓ 142 pass / 0 fail ·
bun run build ✓. 14 new tests: unit coverage of every rejection path in
buildVariantDates/requireVariantDates, plus fetch-stub tests asserting the exact
add/edit request bodies.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AsdP2hvt4XQhYxkdNREXUq

`mna variants add` had no way to set dates, and the API refuses to store a
variant without them, so every invocation came back as a bare `✖ HTTP 400`
(the 400 body carries no `message`, so there was nothing to explain it).

- New `--start-date` / `--end-date` (exact) and `--depart-not-before` /
  `--depart-not-after` / `--return-not-before` / `--return-not-after` /
  `--min-nights` / `--max-nights` (flexible) flags on `variants add` and
  `variants edit`, following the `destinations --start-date/--end-date`
  precedent and reusing `normalizeToIsoDateTime`.
- `variants add` fails fast client-side when no date flag is given, naming
  both shapes, instead of sending a doomed request.
- Half-filled and mixed shapes are rejected locally on both commands — the
  server rejects those too, again without a message. `variants edit` still
  omits `dates` entirely when no date flag is given ("leave alone"), and can
  never emit `dates: null`.
- Skill reference: event location coordinates are `lat`/`lng`, not
  `latitude`/`longitude`. Verified against production — the documented form
  is a 500, the corrected form round-trips.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsdP2hvt4XQhYxkdNREXUq
@akoso
akoso merged commit dbe9c0d into main Aug 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant