Get a serious first-pass review before you push.
The critique you should have done to your own PR before your colleague spends their time on it. Structural issues, logic gaps, test holes.
You're about to open a PR. You know the code works. You're less sure it's the right shape. Paste the diff into LADLE and get a first-pass review focused on structure, logic, missing tests, and the specific concerns your team's reviewer will raise — before they raise them.
THE PROMPT
Do a first-pass code review of the following diff. I'll paste the diff below. Be direct — no "great work overall" preamble. Cover, in order: **Structure** — 2-4 bullets on the shape of the change. Is the abstraction the right one? Is this in the right file? Would you split it differently? **Logic** — bullet list of any bugs, edge cases, or incorrect assumptions. Include line numbers from the diff. If there are none, write "None found." **Missing tests** — bullet list of specific test cases this diff introduces the need for. Reference concrete inputs, not abstract "test error handling". **Nits** — 2-4 style/naming issues. Only include the ones a real reviewer would actually mention. Skip auto-formatter-territory items. **Ship signal** — one sentence: "Ship as-is", "Ship with the missing tests", or "Rework before ship — [specific reason]". Do not add a "let me know if you'd like more detail" close. Just the review. Diff: [PASTE DIFF]
**Structure**
- The migration logic sits in `api/checkout/route.ts` but it belongs in a service module — the route file should be a thin handler.
- Splitting `validateCheckout` and `processCheckout` into two functions is right; the shared `_state` variable between them is not — pass it explicitly.
**Logic**
- Line 47: the `user.email` check is truthy for empty strings — use `user.email?.trim()`.
- Line 82: the `await` inside the `forEach` doesn't actually await; you're firing all promises in parallel and returning before they resolve. Switch to `for...of` or `Promise.all(map(...))`.
**Missing tests**
- Empty cart submitted alongside a valid `user.email` (currently returns 200 with no items).
- Stripe idempotency-key collision (retrying the same request should not double-charge).
- Line 82 concurrency bug — a test that verifies all writes actually completed before response.
**Nits**
- `_state` should just be `state` — the underscore convention isn't used elsewhere in this file.
- `try { ... } catch (e) { throw e; }` on line 61 is a no-op — delete the wrapping.
**Ship signal**
Rework before ship — the line 82 concurrency bug and the empty-cart-with-email case would ship a real regression.