{
  "format": "driftproof-answers/2",
  "receipt_hash": "9f61b33fdfd72f62ba2eca30e2cc4dd84191f0abdab6d47d4f64f9319b8b7056",
  "model_id": "claude-opus-5-5",
  "answers": {
    "02029edc63580049c52c85396ae5a60be907f6732f360725a517eca398f02a86": "## Reading the error\n\n**1. Start with the HTTP status.** The envelope rules say to check the status before the body. A `400` means the server rejected the request during validation, so the request itself needs fixing. It is not a key, scope, plan, credit or concurrency problem. Those return `401`, `403`, `409`, `429`, or a run-level `data.error`.\n\n**2. Treat the code as secondary.** The documented error table lists `BAD_REQUEST` as the code for a 400 validation failure. `INVALID_INPUT` does not appear there, so it is either a newer or more specific code or a drift from the documented contract. Either way, the caller should:\n- branch on `status` (400 means validation failure), not on an exact code string,\n- tolerate unknown codes instead of failing on them,\n- check the OpenAPI spec (`/api/v1/spec.json`) later if it needs to tell `INVALID_INPUT` apart from `BAD_REQUEST`. I haven't checked it, as instructed.\n\n**3. Act on the message.** `Missing brief` names the problem: the request body left out a required brief, the design prompt or description the endpoint expects. The caller should:\n- confirm it sent `Content-Type: application/json` with a real JSON body. A missing header or a malformed body (for example, JSON corrupted by piping through `echo` in zsh) can look like a missing field,\n- look up the field name and location for that endpoint in `endpoints.md` or the spec, add a non-empty brief, and resend.\n\n**4. Resending is safe after the fix.** A 400 at validation means nothing was accepted, so no run started and no credits were spent. The caller doesn't need to cancel or replay anything. It just sends a corrected request. Don't retry the identical body, because it will fail the same way.\n\n**5. No user action is needed** unless the body includes `data.url`. Validation errors normally don't carry one, since this is the caller's mistake to fix, not something the user handles on the Sleek site.",
    "a3dfa1a054ba0b1b39bd32050f8161783f2823ddacc86676c1f52edcce74f1f1": "## How to read this error\n\n**1. Start with the HTTP status.** The Sleek API puts the payload under `data` only on success. A `400` means the request was rejected before any work began. The error fields sit at the top level, so read `code`, `status` and `message` directly from the body, not from `data`.\n\n**2. The `code` doesn't match the documented one.** The skill's error table lists `400` as `BAD_REQUEST` (\"Validation failure\"). This response returned `INVALID_INPUT`, which isn't in that table. Possible explanations:\n- The API has added a more specific code that the skill docs don't list yet.\n- The caller's error-handling code (or a proxy) rewrote the code.\n\nIn either case, a caller that branches only on the literal string `BAD_REQUEST` would miss this response. Branch on the HTTP status, and treat any unrecognized `code` under a `400` as a validation failure. If the exact code matters to you, check it against the OpenAPI spec at `https://sleek.design/api/v1/spec.json` instead of assuming.\n\n**3. The `message` gives the actual cause.** \"Missing brief\" means the request body omitted a required field. This is almost certainly the chat message content on `POST /api/v1/projects/:id/chat/messages`, the endpoint that designs screens from a brief. Common causes:\n- The field was never set, or was empty.\n- The field name was wrong. Use the exact name from the spec or `endpoints.md`.\n- The body wasn't sent as JSON. The request needs `Content-Type: application/json`, or the server won't parse the body and every field looks missing.\n\n**4. What this error is not:**\n- **Not a run-level error.** No run was created, so there's no `runId` to poll or cancel, and nothing like `out_of_credits` or `execution_failed` applies. Those appear inside `data.error` on a run that has started.\n- **Not an auth, plan or rate-limit problem.** Those come back as `401`, `403` or `429`, sometimes with a `data.url` to give the user. A `400` normally has no `data.url`, and you don't need to send the user anywhere.\n- **Probably not a charge.** The request failed validation before any AI work started, so no credits should have been used.\n\n**5. What to do next.** Don't resend the same request, because it will fail the same way. Fix the body so it includes a non-empty brief under the correct field name, confirm the JSON header is set, and then send it as a new request.",
    "e826804f4fb83f1cf6bca94c8934504b20e90f478730dae0fc36922949185031": "## Reading this response\n\n**1. Check the HTTP status first.** A `400` is a client-side validation failure: the request was rejected before any work began. Because it's an error, there's no `data` payload to unwrap. Read the top-level `code`, `status` and `message` fields instead.\n\n**2. Note that the code isn't the documented one.** The documented error table maps HTTP 400 to `BAD_REQUEST`, but this response returned `INVALID_INPUT`. The caller should:\n- Branch on the HTTP status (`400`), not on an exact match against `code`.\n- Treat any unrecognized `code` on a 400 as a validation failure.\n- Log the actual `code` value rather than assuming it.\n\nThe OpenAPI spec at `/api/v1/spec.json` may list `INVALID_INPUT`. Until that's confirmed, it's a mismatch between the docs and the live API.\n\n**3. Use `message` for the specific cause.** `Missing brief` means the request body didn't include a `brief` field the endpoint requires, or the server couldn't see it. Check:\n- That the body actually contains the brief field, with the name and nesting the endpoint expects.\n- That `Content-Type: application/json` was sent. Without it, a valid body may not be parsed, and every field looks missing.\n- That the JSON is well-formed. For example, building it through `echo` in zsh can mangle escaped `\\n` characters.\n\n**4. This is not a run-level error.** It isn't one of the `data.error` codes such as `out_of_credits` or `execution_failed`. The request was rejected at validation, so there is no `runId` to poll or cancel. A 400 normally carries no `data.url` either, so there's no page to send the user to.\n\n**5. Fix the request before retrying.** Resending the same request will fail the same way. Add the missing brief, correct the headers or JSON if needed, and send it again as a new request.",
    "6a672ed61d01cc5d06c144e2059c19d23305b7d135aa76f84253a4fc55ea0ffe": "## Reading the response\n\n**1. HTTP status (400 Bad Request)**\nThe server rejected the request as malformed or incomplete. This is a client-side error: the request itself has to change. It is not an outage or rate limit, so retrying the same request unchanged will fail the same way.\n\n**2. Top-level `code`: `INVALID_INPUT`**\nThis is the machine-readable category, and it's the field your code should branch on. It says the input failed validation. Error-handling logic should key off this code, not the message text, because codes are meant to be stable and messages may be reworded.\n\n**3. `status`: 400**\nThis repeats the HTTP status in the body. It's useful when the body is logged or passed along without its HTTP envelope. It should match the transport status. If it ever didn't, treat that as a separate oddity worth flagging.\n\n**4. `message`: \"Missing brief\"**\nThis is the human-readable detail, and it tells you the specific fix: the request didn't include a required **brief** input. Show it in logs or to developers. Don't parse it programmatically.\n\n## What the caller should do\n\n- **Don't retry automatically.** Treat this as a non-retryable validation failure.\n- **Check the request payload** for the brief field. It might be absent entirely, misnamed (wrong key or casing), nested in the wrong place, or sent as empty/null when a non-empty value is required.\n- **Check the encoding.** For example, confirm a JSON body was sent with the correct `Content-Type` so the server could actually see the field.\n- **Fix the request and resend.**\n\n## Caveats\n\nThese facts don't include Sleek's API documentation. That means I can't confirm:\n- the exact field name or location the service expects for \"brief\"\n- whether other required fields would fail after this one (validators often report only the first error)\n- whether the body includes other fields, such as a field path or details array, that weren't supplied here\n\nCheck Sleek's docs for the endpoint's required parameters to confirm the exact shape.",
    "4c440bf0f9ea57a3d6239c778a1a96f1e8339e273bdbbd3600658f16009a8d53": "## How to read this response\n\n**What it means:** The service rejected the request before doing any work because a required input, the **brief**, was absent. This is a client-side problem with the request, not a server failure.\n\n**Breaking down the fields:**\n\n| Field | Value | Meaning |\n|---|---|---|\n| HTTP status | `400` | Bad Request: the request was malformed or incomplete |\n| `status` (body) | `400` | Repeats the HTTP status in the payload, which is useful if headers are lost in logging |\n| `code` | `INVALID_INPUT` | A machine-readable category. Branch your error handling on this, not on the message text |\n| `message` | `Missing brief` | Human-readable detail that points to the specific problem: no brief was supplied |\n\n**What the caller should do:**\n\n1. **Don't retry the same request.** A 400 is deterministic. Resending identical input will fail the same way, so it doesn't belong in retry or backoff logic. Retries are for 429 and 5xx responses.\n2. **Supply the brief.** Check that the request includes the brief field, that it isn't empty or null, and that it's in the place the API expects (body, query, or form field). Also check that the field name matches the API's documentation exactly, including case.\n3. **Check serialization.** If the brief was set in code but still reported missing, the cause is often a wrong `Content-Type`, a body that was never attached, or a JSON key mismatch such as `Brief` vs `brief`.\n4. **Handle it as a validation error.** Treat `INVALID_INPUT` as something to surface to the user or developer (\"a brief is required\"), not as an outage.\n\n**Caveat:** I'm going only on the fields you supplied. I haven't consulted Sleek's documentation, so the exact field name, format, and placement for \"brief\" should come from their API reference.",
    "c36e6c93d8ec0bd3f55b116ca8cdc98eeba0fa22e7be4cf0371e54efc21a27b6": "## Reading the Sleek 400 response\n\n**What the response says**\n\n| Part | Value | Meaning |\n|---|---|---|\n| HTTP status | `400` | Bad Request. The service rejected the request itself, not because of a server fault. |\n| `status` (body) | `400` | Repeats the HTTP status in the body. Useful when logs only keep the body. |\n| `code` | `INVALID_INPUT` | The machine-readable category: the request failed input validation. |\n| `message` | `Missing brief` | The human-readable detail: a required input called `brief` wasn't there. |\n\n**How the caller should interpret it**\n\n1. **The caller caused it, not the service.** A 400 with `INVALID_INPUT` means the request was malformed. The service is reachable and working.\n2. **Don't retry it unchanged.** Sending the same payload again will get the same 400. Retry and backoff logic should treat this as non-retryable, unlike 429 or 5xx errors.\n3. **Branch on `code`, show `message`.** Program logic should key off `INVALID_INPUT`, which is stable and meant for machines. The `message` text is for people and logs, and its wording may change.\n4. **The specific fix is to supply `brief`.** The request needs to include a `brief` value. Check that:\n   - the field is present and spelled exactly as the API expects (case, nesting);\n   - it isn't empty, `null`, or dropped during serialization (for example, an optional field removed when unset);\n   - the request is sent with the content type the endpoint expects, so the body is actually parsed.\n\n**What it does *not* tell you**\n\n- It doesn't confirm that every other field is valid. Validation may stop at the first missing field, so another 400 could appear once `brief` is added.\n- From these facts alone, it doesn't say where `brief` belongs (top level or nested) or what format it needs. Check Sleek's API docs for that endpoint.\n- Nothing was created or changed on the service side. A rejected validation request should have no side effects.\n\n**Short version:** the request was rejected before processing because the required `brief` input was missing. Add a valid `brief`, then resend. Retrying unchanged won't help.",
    "34893a8144ed7d7d4a99b4a87e1602332d4de83a1e28e34df82e9e6c25d2729e": "## Reading the Sleek 400 response\n\nThe error has three parts, and each tells the caller something different:\n\n| Field | Value | Meaning |\n|---|---|---|\n| HTTP status | `400` | Client error. The server rejected the request as malformed or incomplete. This is not a server outage, auth failure (401/403), or rate limit (429). |\n| `code` | `INVALID_INPUT` | The stable, machine-readable error category. Code should branch on this value. |\n| `status` | `400` | Repeats the HTTP status in the body. It matches the transport status, so nothing unusual is going on. |\n| `message` | `Missing brief` | Human-readable detail. A required input named \"brief\" was not supplied. |\n\n### What the caller should conclude\n\n1. **The request itself is the problem.** The service understood the call and refused it because a required piece of input is absent.\n2. **\"brief\" is the missing input.** Most likely a field called `brief` was omitted, misspelled, nested in the wrong place, or sent empty or null. Some APIs report empty values as \"missing\" too. The supplied facts don't show the endpoint's schema, so check Sleek's documentation for:\n   - the exact field name and casing,\n   - where it belongs (JSON body, query parameter, or form field),\n   - whether an empty string counts as missing.\n3. **Don't retry unchanged.** A 400 is deterministic, so resending the same payload will fail the same way. Retries with backoff are for 429 and 5xx responses, not this one.\n\n### How to handle it in code\n\n- Branch on `code === \"INVALID_INPUT\"` (and the 400 status), not on the exact `message` text. Messages are for humans and may change wording.\n- Surface or log `message` so a developer or user can see which input to fix.\n- If the request is built from user input, validate that `brief` is present and non-empty before sending. That catches this error client-side.\n- Also confirm the payload is actually being serialized and sent. A missing `Content-Type: application/json` header, or a body that never gets attached, can make a field appear \"missing\" even when the code sets it.\n\n**In short:** this means \"your request lacked the required `brief` input.\" Fix the payload and send it again; there's nothing to wait out or escalate.",
    "75a3b9ed71ba320ad8065d1c9305a0f2dec55f8fd1d8063b2956505be191ac64": "Both are calls to the same endpoint, **`POST /api/v1/screenshots`**, authenticated with `Authorization: Bearer $SLEEK_API_KEY` from a key holding the `screenshots` scope. They differ only in framing.\n\n### 1. User shot (for the designer to view)\n\n- **Purpose:** show what the screen looks like on a phone.\n- **Framing:** the default render, which is viewport height and phone-shaped. No extra flag is needed.\n- **Content:** identifies the project and the screen or component to render.\n- **Result:** image bytes, so save the response body to a file.\n\n### 2. Review shot (for judging the long component)\n\n- **Purpose:** check the whole component, including everything below the fold that a viewport-height render would cut off.\n- **Framing:** the same request plus **`fullHeight: true`**.\n- **Content:** exactly **one component per request**. To review several long components, send one review request for each.\n- **Result:** image bytes, showing the component's full length.\n\n### Shared behaviour\n\n- **Check the status first.** A success returns the image. An error returns JSON with `code`, `status` and `message`.\n- **Rate limit.** Screenshots and share cards together are capped at 1000 per hour per organization, counting only API-key requests. Going over returns `429 TOO_MANY_REQUESTS`.\n- **Errors with a link.** A `401`, `403` or `429` response may include `data.url`. Pass that link to the designer unchanged, since it leads to the page where they can fix the problem.\n- **Field names.** I've left out the exact body fields for identifying the project and component. They're in `endpoints.md` and the OpenAPI spec at `https://sleek.design/api/v1/spec.json`.",
    "c9f6516f7e7a23a07f65da965f7740566f6077d8a5611c83635f94b248a698ab": "Both are calls to the same endpoint, **`POST https://sleek.design/api/v1/screenshots`**, authenticated with `Authorization: Bearer $SLEEK_API_KEY` (the key needs the `screenshots` scope) and sent with a JSON body. They differ in framing and purpose.\n\n### 1. User shot (for showing the designer)\n\n- **Framing:** the default render. Leave `fullHeight` off. The image is clipped to the phone viewport height, so it looks like the screen as it would appear on a device.\n- **Target:** the project and the screen or component the designer wants to see.\n- **Use:** this is the image you present to the designer as the \"what it looks like\" view.\n\n### 2. Review shot (for checking the long component)\n\n- **Framing:** the same request with **`fullHeight: true`** added. The render extends to the component's full content height, so the parts below the fold are visible too.\n- **Target:** **exactly one component per request.** If more than one component needs a full-height review, send a separate request for each.\n- **Use:** this is the image you check the design against, looking for overflow, spacing, and content at the bottom of the long component. It's a QA artefact, not a presentation image.\n\n### What to expect back\n\n- **Success:** the response body is raw image bytes, not a JSON `data` envelope. Write it straight to an image file.\n- **Errors:** you get JSON with `code`, `status`, and `message`. If `data.url` is present (on `401`, `403` or `429`), share it with the designer as-is.\n- **Rate limit:** screenshots and share cards together are capped at 1000 per hour per organization. Two shots are well within that.\n\nFor exact body field names, such as how to identify the project and component, check the OpenAPI spec at `https://sleek.design/api/v1/spec.json`. I haven't fetched it here.",
    "618ec3b3dbc1a644223baaa29a08237b66df290bfb3499759372e7f8dc6fcf2b": "Both are requests to the same endpoint, **`POST /api/v1/screenshots`**, authenticated with `Authorization: Bearer $SLEEK_API_KEY`. The key needs the `screenshots` scope. They differ only in framing.\n\n## 1. User shot (the one you show the designer)\n\n- **Purpose:** a realistic, phone-shaped view of the screen as a user would first see it.\n- **Framing:** the default render. It is cropped to viewport height, so anything below the fold is cut off, just like on a real device.\n- **Request:** name the project and the component or screen to render, and leave `fullHeight` unset. No extra flags are needed.\n- **Use:** hand this image to the designer or stakeholder as the presentation shot.\n\n## 2. Review shot (the one you judge the long component against)\n\n- **Purpose:** to inspect the whole component, including everything below the fold, for layout breaks, spacing drift, truncated lists, or broken footers.\n- **Framing:** the same render with `fullHeight: true`. The image extends to the component's full scrollable height instead of stopping at the viewport.\n- **Request:** same project/component targeting, plus `fullHeight: true`. Include **one component per request**. If several long components need review, send a separate request for each.\n- **Use:** an internal QA artefact for your own review. It's not the image to present as \"what the user sees.\"\n\n## Behaviour common to both\n\n- A successful response returns **raw image bytes**, not a JSON `data` envelope, so save the body directly as an image file.\n- Errors come back as JSON with top-level `code`, `status`, and `message`. A `401`, `403`, or `429` may include a `data.url` to pass along to the user as-is.\n- Renders are capped at **1000 screenshots and share cards combined per hour per organization** (API-key requests only). Exceeding it returns `429 TOO_MANY_REQUESTS`.\n- Component and project ids are opaque 11-character tokens. Take them from the response fields they arrived in, such as the components list for the project, because an id alone doesn't say what it identifies.\n\nFor the exact request-body field names beyond `fullHeight`, check the screenshots contract in `endpoints.md` or the OpenAPI spec at `https://sleek.design/api/v1/spec.json`.",
    "2d7a504edf6f6511b669ee3b93de8633ff2dd07ac8218077fa316bc0c1d26583": "These are two separate requests because they answer different questions.\n\n## 1. Phone screenshot: what the user actually sees\n\n**Goal:** Show the design as a real person sees it when the page loads on a phone.\n\n- **Viewport:** Use a common phone size, such as 390×844 CSS pixels (a modern iPhone) or 360×800 (a typical Android).\n- **Pixel density:** Use a device scale factor of 2× or 3× so text and icons look crisp, like they do on a real high-DPI screen.\n- **Mobile emulation:** Use a mobile user agent and touch emulation. This makes responsive breakpoints, mobile-only layouts, and hover-free states render correctly.\n- **Capture area:** Capture only the visible viewport, the area \"above the fold.\" Don't capture the whole page. What matters is the first impression on one screen.\n- **State:** Wait until fonts, images, and animations have settled. Decide whether to include or dismiss overlays like cookie banners, depending on what the designer wants to judge.\n- **Output:** One image at true phone proportions, suitable for mockups or a device frame.\n\n## 2. Full-height component review: the whole element, top to bottom\n\n**Goal:** Let the designer check the entire long component, including spacing, alignment, and consistency, without scrolling through many screenshots.\n\n- **Target:** Capture the component itself, not the page or the viewport. Picking it by selector crops away unrelated page content.\n- **Height:** Capture its full rendered height, even far beyond the viewport, so the image runs from its first pixel to its last.\n- **Width:** Use the width the component is being reviewed at, either the same phone width (for a mobile review) or a desktop width. Keep it consistent so measurements line up.\n- **Lazy content:** Scroll through or trigger lazy-loaded images and deferred sections before capturing. Otherwise the lower parts may show blanks or placeholders.\n- **Sticky and fixed elements:** Hide or neutralize sticky headers and floating buttons. In a tall capture they can be drawn over the middle of the component or repeated.\n- **Output:** One tall image, sized for close inspection and annotation rather than for display on a device.\n\n## The core distinction\n\n| | Phone screenshot | Component review |\n|---|---|---|\n| Question answered | \"What does the user see?\" | \"Is every part of this built right?\" |\n| Scope | Viewport only | One element, full height |\n| Fidelity priority | Real device conditions | Completeness of content |\n| Typical use | Presentations, UX validation | Design QA, redlines, spacing checks |\n\nKeeping them as separate requests prevents a common mistake: a full-page capture misrepresents what the user sees on a phone, and a viewport capture hides most of a long component.",
    "c70b72f9d5c51ce9c52f02cc4e064ed1ca5f4e0cb589ca7b80491c797684d1fa": "Here's how I'd frame the two requests. They differ in **what the frame represents**, not just in size.\n\n---\n\n### 1. User-facing phone screenshot (viewport capture)\n\n**Goal:** Show exactly what a person sees on their phone when the screen loads, before they scroll.\n\n**Conceptual request:**\n- **Device emulation:** A specific phone profile, e.g. a common modern handset at about 390×844 CSS pixels with a device pixel ratio of 3. This also means a mobile user agent, touch enabled, and mobile layout rules (media queries, `viewport` meta) applied.\n- **Capture area:** The **visible viewport only**. Anything below the fold is deliberately excluded, because the point is the first impression.\n- **State:** The page is fully settled. Fonts are loaded, images are decoded, and animations are finished or paused. Any real-world chrome that matters is present, such as sticky headers, cookie banners, or bottom nav, if a real user would see it.\n- **Output:** A single high-DPI image that matches the phone's aspect ratio. It's suitable for dropping into a device frame mockup.\n\n**What it answers:** \"Does the first screen look right and communicate the right thing on a phone?\"\n\n---\n\n### 2. Full-height review of a long component (element capture)\n\n**Goal:** Inspect one long component, like a feed, settings list, long form, or pricing table, from top to bottom in a single artifact for detailed design review.\n\n**Conceptual request:**\n- **Target:** The **component itself**, isolated by its selector or bounding box, not the whole page or viewport.\n- **Capture area:** The component's **full rendered height**, even where it extends well beyond the screen. If the component scrolls internally (its own overflow container), the capture must reveal the entire scroll content. Either expand the container or stitch scrolled segments together. Otherwise only the visible slice gets captured.\n- **Width:** Keep the component at its real layout width, ideally the same mobile width as request #1, so wrapping and spacing are accurate.\n- **State:** All content is rendered. Lazy-loaded items are triggered, placeholders are resolved, and sticky or fixed elements are handled so they don't repeat or overlap in the tall image.\n- **Output:** One tall image, sharp enough for the designer to zoom in on spacing, alignment, and typography.\n\n**What it answers:** \"Is every part of this component correct, consistent, and polished all the way down?\"\n\n---\n\n### Key distinction\n\n| | Phone screenshot | Component review |\n|---|---|---|\n| Scope | Whole screen, as seen | One element |\n| Height | Viewport only (above the fold) | Full content height |\n| Purpose | Realistic user impression | Exhaustive design QA |\n| Main pitfall | Capturing before the page settles | Missing content in internal scroll areas or lazy-loaded sections |\n\nThese should be two separate requests, because merging them would either lose the realistic phone framing or cut off the component.",
    "c83248f8de77cc339c8c4c9f21495d2510e00da6121964d589a807b00699d750": "These are two different captures with different goals. One shows exactly what a person sees on their phone. The other shows the entire component regardless of screen size.\n\n## 1. User-facing phone screenshot (viewport capture)\n\n**Goal:** Reproduce what a real user sees on their device, above the fold, with nothing extra.\n\n- **Viewport:** Set a real phone size in CSS pixels, e.g. 390×844 (a modern iPhone) or 412×915 (a common Android).\n- **Device scale factor:** 2× or 3×, so the image is crisp like a real retina screen.\n- **Mobile emulation:** Turn on touch and mobile mode and use a mobile user agent. This makes responsive breakpoints, mobile-only UI, and `hover: none` styles render as they would on a phone.\n- **Capture area:** The visible viewport only, not the full page. Whatever is below the fold is intentionally cut off, because that's the real first impression.\n- **State:** Wait until fonts, images, and lazy content have loaded and animations have settled. Dismiss or deliberately include cookie banners, depending on what the designer wants to see.\n- **Output:** One phone-shaped image, optionally placed in a device frame for presentation.\n\n## 2. Full-height component review (element capture)\n\n**Goal:** Inspect the whole long component from top to bottom, including the parts a user would have to scroll to reach.\n\n- **Target:** Scope the capture to the component's own element (by selector), not the page, so surrounding chrome is excluded.\n- **Height:** Capture the element's full scroll height. If the component scrolls internally (a fixed-height container with `overflow: auto`), first expand that container temporarily so all its content lays out, or stitch together several scrolled segments.\n- **Width:** Keep the same mobile width as the first capture so the layout matches what users get. Add a desktop-width version if the review covers breakpoints.\n- **Fixed/sticky elements:** Neutralize sticky headers and floating buttons, or note them. Otherwise they can repeat or hide content in a tall capture.\n- **Lazy content:** Scroll through the component, or force-load it, so images and virtualized lists are fully rendered before capturing.\n- **Output:** One tall image (it may be thousands of pixels high) for detailed spacing, typography, and consistency review rather than \"what the user sees.\"\n\n## The key distinction\n\n| | Phone screenshot | Component review |\n|---|---|---|\n| Scope | Whole viewport | Single element |\n| Height | Fixed to the screen | Full content height |\n| Purpose | Realistic user view | Complete design inspection |\n| Below-the-fold content | Excluded on purpose | Included |",
    "211f1743b2480c5afb18224fed41707bb14e04c9a5adb9e0356befea2566d281": "Here's how I'd frame the two requests. They differ in what they're trying to show, so they need different capture settings.\n\n---\n\n### 1. User-facing phone screenshot (viewport capture)\n\n**Goal:** Show exactly what a person sees on their phone when the screen loads, with nothing extra.\n\n**Conceptual settings:**\n- **Viewport:** A realistic phone size in CSS pixels, e.g. ~390×844 for a modern iPhone or ~412×915 for a common Android device.\n- **Device scale factor:** 2× or 3×, so the image is crisp like a real retina screen.\n- **Mobile emulation:** Touch enabled and a mobile user agent, so responsive breakpoints, mobile nav, and touch-specific styles render as they would on a device.\n- **Capture area:** The visible viewport only, not the full page. Whatever sits below the fold is intentionally cut off, because that is the real first impression.\n- **State:** Wait for fonts, images, and layout shifts to settle. Dismiss or deliberately include overlays (cookie banners, install prompts) depending on whether the designer wants the \"real\" or the \"clean\" view.\n- **Optional framing:** Add a device frame or status bar afterward if it's for a presentation, not for pixel review.\n\n**Output:** One image representing a single phone screen.\n\n---\n\n### 2. Full-height review of a long component (element/full-length capture)\n\n**Goal:** Let the designer inspect the entire component top to bottom in one image, even though no real screen would show it all at once.\n\n**Conceptual settings:**\n- **Target:** The component itself, scoped to its bounding box rather than the whole page. This avoids headers, footers, and unrelated content.\n- **Height:** Capture the component's full scroll height, not just the visible portion. If the component scrolls internally (a fixed-height container with overflow), it has to be expanded or its scrollable content captured, otherwise you'd get only the visible slice.\n- **Width:** Use the same width as the context being reviewed, e.g. the phone width from request #1 if it's the mobile layout, so line wrapping and spacing match.\n- **Scale factor:** Usually 2× for detail. Very tall components can produce huge images, so this may need to drop to 1×.\n- **Content readiness:** Trigger lazy-loaded images and any scroll-triggered content before capture, or those sections will show up blank.\n- **Sticky/fixed elements:** Neutralize them. A sticky header inside the component can otherwise appear repeated or misplaced in a stitched full-length image.\n- **Animations:** Pause or skip them, so every section is captured in its final state.\n\n**Output:** One tall image of the whole component, meant for detailed review rather than as a realistic device view.\n\n---\n\n### Key distinction to communicate to the designer\n\n| | Phone screenshot | Full-height component |\n|---|---|---|\n| Represents | Real user's first view | Complete component anatomy |\n| Bounds | Viewport only | Component's full scroll height |\n| Fold matters? | Yes, it's the point | No, ignore the fold |\n| Realism | High (device emulation) | Diagnostic, not realistic |\n\nIf both images use the same width and scale factor, the designer can line them up and see exactly where the fold falls inside the full component.",
    "21a74db74dece1cfee5e22c54c73bf77e86bf2cf9a0229f601de9cd9313cbe18": "The designer needs two different captures, and they should be separate requests because they answer different questions.\n\n## 1. User-facing phone screenshot\n\n**Purpose:** Show exactly what a person sees on their phone when the screen loads.\n\n**Conceptual request:**\n- **Emulate a real device:** Use a phone-sized viewport (for example, about 390×844 CSS pixels for a modern iPhone) with a high pixel density (2× or 3×). Turn on mobile and touch emulation and use a mobile user agent so responsive layouts, mobile navigation and touch-specific styles render as they would on a phone.\n- **Capture the viewport only:** Grab just the visible screen, not the full page. The point is the first impression above the fold, with sticky headers, bottom bars, cookie banners and similar elements where a user would actually see them.\n- **Use a realistic state:** Let fonts, images and above-the-fold content finish loading. Leave the page scrolled to the top unless the designer asked for a specific scroll position.\n- **Output:** One crisp, device-resolution image that could be framed in a phone mockup.\n\n## 2. Full-height review of a long component\n\n**Purpose:** Let the designer inspect one component from top to bottom in a single image, even though it's taller than any screen.\n\n**Conceptual request:**\n- **Target the element, not the page:** Capture only that component's bounding box so surrounding layout doesn't distract.\n- **Capture its full height:** Include everything beyond the visible viewport, so the image is as tall as the component itself rather than cropped at the screen edge.\n- **Choose the layout width on purpose:** Set the viewport width to the breakpoint under review (the same phone width if it's the mobile design, or a desktop width otherwise). The width determines how the component wraps.\n- **Make the content complete:**\n  - Scroll through or trigger lazy-loaded images and content first.\n  - Expand anything collapsed if the review needs it.\n  - Disable animations and transitions so nothing is captured mid-motion.\n- **Neutralize viewport-relative artifacts:** Sticky or fixed elements can repeat or overlap in tall captures. Elements sized in `vh` units can also stretch oddly. Hide or account for these.\n- **Mind the resolution:** A very tall image at 3× density can get huge. 1× or 2× is usually enough for review.\n- **Output:** One tall image of just the component, for detailed critique rather than a device mockup.\n\n## Key distinction\n\n| | Phone screenshot | Component review |\n|---|---|---|\n| Scope | Visible viewport | One element |\n| Height | Screen height | Full element height |\n| Goal | Realistic user view | Complete inspection |\n| Density | Device-true (2–3×) | Practical (1–2×) |",
    "42f83e15fc7858924c68502db3ef45af49908b0b0b56f405a743f5957f7301ec": "Here's how I'd frame the two requests. They answer different questions, so they're configured differently.\n\n## 1. User-facing phone screenshot (viewport capture)\n\n**Purpose:** Show exactly what a person sees on their phone when the page loads, with no scrolling.\n\n**Conceptual settings:**\n- **Viewport:** A realistic phone size in CSS pixels, e.g. ~390 × 844 (a modern iPhone) or ~412 × 915 (a common Android).\n- **Device scale factor:** 2–3× so the image is crisp, like a real retina screen.\n- **Mobile emulation:** Mobile user agent, touch enabled, and `isMobile` on, so responsive breakpoints, mobile-only layouts and touch styles apply.\n- **Capture area:** Viewport only, not full page. Content below the fold is intentionally cut off, because that's the real first impression.\n- **State:** Fonts and images fully loaded, animations settled, and any intended UI visible (sticky header, cookie banner, nav bar). Decide deliberately whether overlays should appear.\n\n**Output:** One phone-shaped image. Suitable for decks, app-store-style mockups, or dropping into a device frame.\n\n## 2. Full-height review of a long component (element capture)\n\n**Purpose:** Let the designer inspect the *entire* component top to bottom (spacing, alignment, every section) regardless of screen height.\n\n**Conceptual settings:**\n- **Target:** The specific component, selected by an element selector, rather than the whole page or the viewport.\n- **Capture area:** The element's full scroll height, rendered as one tall continuous image with no clipping at the fold.\n- **Width:** Match the width the component is being reviewed at. Use the same phone width for a mobile review, or a desktop width if that's the subject.\n- **Isolation:** Exclude surrounding page chrome. Watch for sticky or fixed elements (headers, floating buttons) that can repeat or overlap in tall captures, and hide them if they distract.\n- **Lazy content:** Make sure lazy-loaded images and sections below the fold have rendered before capturing. Otherwise the lower part appears blank.\n- **Inner scrolling:** If the component scrolls internally (fixed-height container with `overflow: auto`), it may need to be temporarily expanded to its natural height to be captured in full.\n\n**Output:** One tall, possibly very long image of just that component. Meant for close review and annotation, not presentation.\n\n## Key distinction\n\n| | Phone screenshot | Component review |\n|---|---|---|\n| Question answered | \"What does the user see?\" | \"Is every part of this built right?\" |\n| Scope | Whole page, visible viewport | One element |\n| Height | Fixed to the screen | Full content height |\n| Mobile emulation | Essential | Only if reviewing the mobile layout |\n| Audience | Stakeholders / presentation | Designer / QA detail review |\n\nKeep them as two separate requests. Combining them, for example taking a full-page shot and cropping it, loses either the true above-the-fold framing or the clean, isolated component view.",
    "8e34b8ca00841b2595f885f7201b5b413ef6e5a744f620ab039463e04145bd29": "Sleek ids are opaque 11-character tokens like `V1StGXR8Z5j` with **no type prefix**. Nothing in the string tells you whether it names a project, screen, component, version or theme. The only reliable source of an id's type is **the response field it arrived in**, so keep that information alongside the value from the moment you read it.\n\n## How to retain the types\n\n1. **Bind each id to its field name when you parse the response.** Write the body to a file and parse the file, not `echo`. Then store each value under a name that mirrors its source field:\n   - `projectId` ← the response's `projectId` field\n   - `screenId` ← the response's `screenId` field\n\n   Don't collapse them into a generic list like `ids = [...]` or a bare `id` variable. Once the field name is gone, the type is gone too.\n\n2. **Never infer a type from the value.** Length, characters and position don't tell you anything. Two ids from different resource types look the same.\n\n3. **Keep the provenance in your plan.** When you write down the update steps, label each id by role and origin. For example: \"project `V1StGXR8Z5j` (from `projectId`), screen `…` (from `screenId`).\" That way a later step can't quietly swap them.\n\n4. **Place each id in the URL slot that matches its type.**\n   - `projectId` goes in the `:id` segment, as in `/api/v1/projects/:id/...`. This covers chat messages that request the screen edit, run polling, and component listing.\n   - `screenId` only goes where a screen-typed parameter is expected. The project paths take `:componentId` rather than `:screenId`. Before you put a screen id into a component slot, check the endpoint contract in the OpenAPI spec (`/api/v1/spec.json`) or `endpoints.md`. Treat it as compatible only if the contract says so.\n\n5. **Re-derive the types from each new response.** Later responses, such as a run result or a component listing, may return ids in new fields. Give each new value the type of its new field. Don't assume it's the same kind of id as an earlier value just because the strings match or sit in similar positions.\n\n6. **Don't send a mismatched id and wait for the error.** A swapped id may come back as `404 NOT_FOUND`. It could also resolve to some other real resource and silently target the wrong thing. Keeping the types bound up front avoids both outcomes.\n\n## In short\n\nRead the ids as `projectId` and `screenId`, keep those labels attached through the whole plan, and route each one only to path slots whose contract expects that type.",
    "19ed48b2bcc72189f944204da9a403c57d1c96ae38a5350e45088ee226ebdb67": "Sleek ids are opaque 11-character tokens like `V1StGXR8Z5j`. Nothing in the id itself says whether it names a project, screen, component, version or theme. **The type comes only from the response field the id arrived in**, so you have to carry that label forward yourself.\n\n## How to retain the types\n\n1. **Label each id when you parse the response.** Read `data.projectId` into something named `projectId` and `data.screenId` into something named `screenId`, such as distinct variables or a small record like `{ projectId: \"...\", screenId: \"...\" }`. Don't keep them as two loose strings or in a bare list, where their order is the only clue to which is which.\n\n2. **Never infer the type from the id's value.** Both are 11 characters from the same alphabet, so length, characters or \"it looks like the one from earlier\" can't tell them apart. If a label is lost, go back to the original response instead of guessing.\n\n3. **Parse from a file, not a pipe.** Save the body (for example `curl -o run.json`) and read the fields from that file. Piping JSON through `echo` can corrupt it, because zsh turns escaped `\\n` into real newlines. A broken parse is a common way to lose track of which field held which id.\n\n4. **Map each label to the matching slot when planning the update.**\n   - `projectId` goes in the project path segment, for example `POST /api/v1/projects/:id/chat/messages` for the update request, or `GET /api/v1/projects/:id/components/...` to fetch results.\n   - `screenId` goes only where a screen is referenced, such as naming the screen to change or a per-screen fetch or screenshot.\n\n5. **Confirm field names in the spec.** The component endpoint's path parameter is `:componentId`. Before putting `screenId` there, check `https://sleek.design/api/v1/spec.json` to confirm the two refer to the same thing, instead of assuming they're interchangeable.\n\n6. **Keep labelling new ids.** Responses from the update, such as a `runId`, a new version or component ids, are also untyped strings. Record each under its field name as it arrives, and keep the earlier `projectId` and `screenId` labelled separately.\n\n## Why it matters\n\nSwapping the two ids doesn't fail visibly at your end. The API simply receives a valid-looking token in the wrong slot. That usually produces a `404 NOT_FOUND`, or, if the token happens to resolve to something, an update applied to the wrong resource. Keeping every id labelled by its source field from parse to request prevents this.",
    "d8dcb7363d60b3cdd769e8ec3ba8c8a7897b3143538a35ff80520a3971e6c560": "# Keeping `projectId` and `screenId` distinct\n\nSleek ids are opaque 11-character tokens like `V1StGXR8Z5j`, with **no type prefix**. A project id and a screen id look identical, so the id itself can't tell you which is which. The type comes only from **the response field it arrived in**, so keep that label attached from the moment you parse the response.\n\n## How to do it\n\n1. **Record each id under its source field name, right away.** When you parse the response, store the values as named pairs, for example `projectId = \"…\"` and `screenId = \"…\"`. Never keep them as an unlabeled list like \"the two ids from the response.\"\n\n2. **Never reclassify an id by inspecting it.** Length, characters, and ordering carry no meaning. If a label is lost, go back to the original response file and re-read the field name. Don't guess.\n\n3. **Keep the labels in the plan.** Each step of the update plan should name its ids by type, for example:\n   - Send the edit through `POST /api/v1/projects/{projectId}/chat/messages`, with the message naming the screen to change.\n   - Poll `GET /api/v1/projects/{projectId}/chat/runs/{runId}`. The `runId` is a third opaque id, so label it too.\n   - Fetch the updated screen with `GET /api/v1/projects/{projectId}/components/{screenId}`, if the screen is addressed as a component there.\n   - Take a review shot of that one screen to check the result.\n\n4. **Match each id to its slot in the URL.** The project path parameter (`:id`) takes the project id. The `:componentId` parameter takes the screen or component id. A swapped id usually returns a `404 NOT_FOUND`, but don't rely on that to catch mistakes. Check the label before you build the URL.\n\n5. **Label new ids as they appear.** Run responses and component listings return more ids, such as runs, versions, components, and themes. Give each one its field-derived name immediately, the same way you did for `projectId` and `screenId`.\n\n## A caveat\n\nThe supplied facts don't say whether a \"screen\" maps exactly to a \"component\" in the endpoint paths. Before using `screenId` as `:componentId`, check the field names in the component list or detail response. You can also check the OpenAPI spec at `/api/v1/spec.json` to confirm the mapping, rather than assuming it.",
    "7cd41a9f175e6434f2e63d4eed13093e726aabc17853fc94334635da215eecf6": "Sleek ids carry no type information. Every id is an opaque 11-character token like `V1StGXR8Z5j`, with no prefix, so nothing in the string tells you whether it names a project, screen, component, version or theme. The **response field it arrived in** is the only source of its type. Keep that field name attached to the value for the whole plan.\n\n## How to keep the types\n\n1. **Label at the point of capture.** When you read the response, bind each value to a name taken from its field: `projectId = <value of projectId>` and `screenId = <value of screenId>`. Don't store bare strings like \"id1\" and \"id2\", and don't keep an unlabeled list of ids.\n\n2. **Never infer a type from the value.** Both values have the same length and alphabet. Telling them apart by eye, by order, or by \"the one that looks like a project\" will eventually swap them.\n\n3. **Bind each id to the slot that matches its type.**\n   - `projectId` goes only into `:id` slots, such as `/api/v1/projects/:id/...`. A screen update is sent as a chat message to `POST /api/v1/projects/:id/chat/messages`, so `:id` must be the `projectId`.\n   - `screenId` goes only into slots or body fields that name a screen or component. The endpoint table uses `:componentId` (`/api/v1/projects/:id/components/:componentId`), and screenshots are taken per component. Treating `screenId` as the `componentId` is therefore a mapping you make, not something the id itself says. Before relying on it, confirm in the OpenAPI spec (`/api/v1/spec.json`) that the field you received is the identifier those slots expect.\n\n4. **Carry the labels through follow-up responses.** A chat run returns new ids, such as a `runId` and possibly new component or version ids. Label each one by its field as it arrives. For example, the `runId` goes only into `/chat/runs/:runId`. If a later response returns the screen under a different field name, record that alias explicitly rather than assuming the two names match.\n\n5. **Write the plan with named placeholders.** For example:\n   - `POST /api/v1/projects/{projectId}/chat/messages` with a brief naming the screen to change.\n   - `GET /api/v1/projects/{projectId}/chat/runs/{runId}` until the run finishes.\n   - A review shot (`fullHeight: true`) of `{screenId}` to judge the result, then a user shot to show the user.\n\n   Named placeholders make it obvious if a value ever lands in the wrong slot.\n\n## What a mix-up looks like\n\nIf the two ids are swapped, the API usually responds with `404 NOT_FOUND`, because no project exists with the screen's id. That `404` could also mean a real missing resource. When you see one after substituting ids, check your field-to-slot mapping before concluding the resource is gone.",
    "a2b89b4fe02df09c761c0a12b636e6f18d033c4caabb2e6fa3dfa8d5bfb48c1f": "# Retaining the Types of `projectId` and `screenId`\n\nThe rule is to treat both values as **opaque strings** and pass them through exactly as you received them.\n\n## 1. Keep them as strings\n- Store and send them as strings, even if a value looks numeric (e.g. `\"00412938571\"`).\n- Converting to a number loses leading zeros, can overflow or lose precision, and changes the JSON type from `\"...\"` to a bare number. The API may reject that or misread it.\n\n## 2. Copy them verbatim\n- Don't trim, pad, change case, reformat, or split them.\n- Don't infer meaning from any part of the value, like a prefix or embedded date. \"Opaque\" means the only valid operations are storing, comparing for equality, and sending back.\n- Take them from the response and put them into the update request unchanged.\n\n## 3. Keep the two IDs from being swapped\nBoth are 11-character strings, so a plain `string` type won't catch a mix-up. Give each one a distinct type:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: ScreenChanges;\n}\n```\n\nOther languages have equivalents, such as newtypes in Rust or Haskell, `NewType` in Python, or small wrapper classes. Use named fields rather than positional arguments so `update(screenId, projectId)` can't happen silently.\n\n## 4. Validate lightly, at the boundary\n- When the response is parsed, check only that each value is a string of length 11. Then convert it to the branded type.\n- Don't add pattern checks the API hasn't documented. A guessed format will break when the issuer changes it.\n\n## 5. Serialize correctly\n- **JSON body:** emit them as JSON strings: `{\"projectId\": \"...\", \"screenId\": \"...\"}`.\n- **URL path or query:** percent-encode them when building the URL (e.g. `encodeURIComponent`). Keep the stored value unencoded and encode only at the point of use.\n- **Logs and storage:** use text or varchar columns, never integer columns.\n\n## 6. Carry them through the plan unchanged\nIn the update plan, treat both IDs as read-only identity fields that select which screen to update. Only the `changes` payload should be modified. The plan should never generate new IDs or edit existing ones.\n\n**In short:** parse once into distinct string types, never transform them, and serialize them back as strings.",
    "2b0babd5c708c1f402cec036b9bfb5fad8c893dd935f481f374d262cf82fa01f": "# Retaining the types of `projectId` and `screenId`\n\nBoth values are **opaque 11-character identifiers**. Keep them as exact strings from the moment you read the response until you send the update request.\n\n## 1. Treat them as strings, never numbers\n\nEven if an ID looks numeric (e.g. `\"04718293651\"`), do not convert it.\n\n- **Leading zeros are lost:** `\"04718293651\"` becomes `4718293651`, which is only 10 characters.\n- **Some IDs aren't numeric at all:** \"Opaque\" means you can't assume the character set, so parsing can fail or return `NaN`.\n- **Large numbers lose precision:** Some languages and JSON parsers silently round large integers.\n\nIn JSON, always quote them:\n\n```json\n{ \"projectId\": \"04718293651\", \"screenId\": \"aB3xK9mQ2Lp\" }\n```\n\n## 2. Pass them through verbatim\n\n\"Opaque\" means you don't interpret, build, or change them:\n\n- Don't trim, pad, change case, or reformat them.\n- Don't infer meaning from parts of the ID, such as a prefix or embedded date.\n- Don't generate or guess IDs. Use only the values from the supplied response.\n- When placing them in a URL path or query, URL-encode them as strings. Don't rewrite them.\n\n## 3. Keep the two IDs distinct\n\nBoth are 11-character strings, so it's easy to swap them by accident. Give each its own type so the compiler catches mix-ups. A TypeScript example:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\nfunction asProjectId(v: unknown): ProjectId {\n  if (typeof v !== \"string\" || v.length !== 11) throw new Error(\"Invalid projectId\");\n  return v as ProjectId;\n}\nfunction asScreenId(v: unknown): ScreenId {\n  if (typeof v !== \"string\" || v.length !== 11) throw new Error(\"Invalid screenId\");\n  return v as ScreenId;\n}\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: Record<string, unknown>;\n}\n```\n\nEquivalents in other languages include a `NewType` in Python, a value class in Kotlin, or a wrapper struct in Go or Rust.\n\n## 4. Validate the shape, not the meaning\n\nAt the point where you read the response, check only that:\n\n- the value is a string, and\n- it has exactly 11 characters.\n\nDon't validate it against a numeric range or a pattern you've guessed. If a check fails, stop and report the problem. Don't try to \"fix\" the value.\n\n## 5. Carry the types through the plan\n\nIn the update plan, copy the IDs straight from the parsed response into the request, with no conversion in between:\n\n1. Parse the response and wrap each ID in its branded type (`asProjectId`, `asScreenId`).\n2. Build the update plan using those typed values.\n3. Serialize the request as JSON, where the IDs remain quoted strings, or put them in the URL path, e.g. `/projects/{projectId}/screens/{screenId}`.\n4. Optionally, check that the IDs echoed back in the update response match the ones you sent.\n\nIn short, read the IDs as strings, validate only their type and length, give each a distinct type, and send them unchanged.",
    "d931e77a106b98661cb81b3ce78d6517e0407f73f6bcc5ac793a6e7692305e5f": "# Retaining `projectId` and `screenId` Types in a Screen Update\n\nBoth fields are **opaque 11-character identifiers**. Treat each one as an exact string token you received and must hand back unchanged. Don't treat them as data you can interpret.\n\n## 1. Keep them as strings, always\n\n- Store, pass, and serialize them as **strings**, even if a value happens to look numeric (e.g. `\"04812736591\"`).\n- Never coerce them to numbers. Coercion drops leading zeros, can lose precision, and changes the JSON type from `\"...\"` to a bare number.\n- In JSON payloads, emit them as quoted string values:\n  ```json\n  { \"projectId\": \"a1B2c3D4e5F\", \"screenId\": \"Zx9_Qw-8LmN\" }\n  ```\n\n## 2. Pass them through verbatim\n\n\"Opaque\" means you make no assumptions about structure. So:\n\n- **No transformation:** don't trim, pad, lowercase or uppercase, URL-decode, or re-encode them.\n- **Case is significant:** `aB3...` and `ab3...` are different IDs.\n- **No inference:** don't split them into segments, derive one from the other, or guess a format such as a hex, base64, or timestamp prefix.\n- **Copy, don't reconstruct:** take the exact values from the supplied response. Don't retype or regenerate them.\n- **Escape only at the transport layer:** if an ID goes into a URL path or query, apply standard encoding there. Keep the stored value raw.\n\n## 3. Keep the two IDs distinct\n\nThey share the same shape, so they're easy to swap by accident. Give each one its own type:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: Partial<ScreenFields>;\n}\n```\n\nThe compiler will then reject passing a `ScreenId` where a `ProjectId` is expected. In other languages, use a newtype or wrapper class for the same effect.\n\n## 4. Validate lightly, at the boundary only\n\n- When the IDs arrive, it's reasonable to check that each one is a string of length 11.\n- Don't enforce a character-set or pattern rule unless the API documents one. Opaque means the format may change.\n- After that boundary check, never modify the values.\n\n## 5. Planning the update\n\n1. Extract `projectId` and `screenId` from the response as typed strings (`ProjectId`, `ScreenId`).\n2. Build the update plan by referencing those exact values. Put the changes in a separate field, so the IDs act as the target and the changes act as the payload.\n3. Serialize with both IDs as JSON strings.\n4. Optionally, check before sending that each ID equals the originally received value, compared byte for byte.\n\n**In short:** treat both IDs as distinct, immutable, case-sensitive string tokens. Validate their length once on arrival, never parse or coerce them, and send them back exactly as received."
  },
  "draws": [
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 0,
      "generation_hash": "02029edc63580049c52c85396ae5a60be907f6732f360725a517eca398f02a86",
      "answer_sha256": "02029edc63580049c52c85396ae5a60be907f6732f360725a517eca398f02a86",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 status first, reads the top-level code and message, treats the 400 as a rejected request rather than success, and says to add the brief and resend rather than retry the same body.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: it checks the 400 status first, reads the top-level code and message, treats it as validation failure rather than success, and says to add the brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1 (checks HTTP 400 status first), 2 (reads top-level code/message fields), and 4 (fix missing brief before resending, no blind retry); criterion 3 is not explicitly addressed as a data-wrapped success interpretation, so only partially covered and scored 0.75.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP status first, reads the top-level code and message, treats the 400 as validation rather than success, and says to add the brief and resend without retrying the identical body.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP status first, reads the top-level code and message, does not treat the 400 as success, and says to add the brief and resend rather than retry the same body.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 1,
      "generation_hash": "a3dfa1a054ba0b1b39bd32050f8161783f2823ddacc86676c1f52edcce74f1f1",
      "answer_sha256": "a3dfa1a054ba0b1b39bd32050f8161783f2823ddacc86676c1f52edcce74f1f1",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response checks the HTTP 400 status first, reads code/status/message from top-level fields rather than data, explicitly says the payload is not a data-wrapped success, and tells the caller to fix the missing brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response checks the 400 HTTP status first, reads code/status/message from top-level fields rather than data, explicitly says it is not a data-wrapped success, and tells the caller to fix the missing brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four rubric points: checks HTTP 400 first, reads top-level code/status/message, explicitly says not to read from data, and says to fix the missing brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 status first, reads top-level code/status/message rather than data, and says to fix the missing brief before resending; the rubric's 'ask for' option is not explicit, so criterion 4 is at least partly met while the rest are satisfied.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response checks the 400 HTTP status first, reads code/status/message from top-level fields rather than data, explicitly states the body is not data-wrapped success, and tells the caller to fix the missing brief before retrying.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 2,
      "generation_hash": "e826804f4fb83f1cf6bca94c8934504b20e90f478730dae0fc36922949185031",
      "answer_sha256": "e826804f4fb83f1cf6bca94c8934504b20e90f478730dae0fc36922949185031",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: says to check the HTTP status first, reads top-level code/status/message, notes a 400 has no data payload to unwrap, and says to add the missing brief before retrying.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Criteria 1 (checks HTTP 400 first), 2 (reads top-level code/status/message), and 4 (fix missing brief before retrying) are met; criterion 3 is met implicitly since it states there is no data payload to unwrap on the error response.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response checks the HTTP 400 status first (criterion 1), reads top-level code/status/message fields (criterion 2), explicitly states there is no data payload to unwrap (criterion 3), and tells the caller to add the missing brief before retrying (criterion 4).\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: checks the 400 status first, reads top-level code/status/message, does not treat it as data-wrapped success, and says to add the missing brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response checks the HTTP 400 status first, reads top-level code/status/message, notes there is no data payload to unwrap, and says to add the missing brief before resending.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 0,
      "generation_hash": "6a672ed61d01cc5d06c144e2059c19d23305b7d135aa76f84253a4fc55ea0ffe",
      "answer_sha256": "6a672ed61d01cc5d06c144e2059c19d23305b7d135aa76f84253a4fc55ea0ffe",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first and reads top-level code/message fields, and advises against blind retry while fixing the missing brief. Misses point 3 since it never addresses the body not being data-wrapped success, and point 4 is only partly met (fix-and-resend, no explicit ask-for-brief).\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads top-level code INVALID_INPUT and message, and says not to retry unchanged, satisfying criteria 1, 2, and 4. It never treats the error as data-wrapped success, but it only partly covers criterion 4 by suggesting a fix rather than asking for the brief.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP status first, reads top-level code and message fields, and says not to retry unchanged; it asks for and fixes the missing brief before retrying, but never frames the 400 as data-wrapped success, so the rubric is met on three of four points.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks the HTTP 400 status first, reads the top-level code, status, and message fields, treats the result as an error rather than wrapped success, and says to fix the missing brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks the HTTP 400 status first, reads the top-level code, status, and message fields, treats the result as an error rather than success, and advises fixing the missing brief before resending.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 1,
      "generation_hash": "4c440bf0f9ea57a3d6239c778a1a96f1e8339e273bdbbd3600658f16009a8d53",
      "answer_sha256": "4c440bf0f9ea57a3d6239c778a1a96f1e8339e273bdbbd3600658f16009a8d53",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks the 400 HTTP status first, explains the top-level code, status, and message fields, treats it as a failure rather than success, and says to supply the brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Covers all four criteria: it reads the 400 HTTP status and top-level code, message, and status fields, treats the response as an error rather than success, and says to supply the brief and not retry the same request.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 status first, reads the top-level code, status, and message fields, treats it as a rejection rather than success, and says to supply the missing brief before any retry.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (checks HTTP 400 status first), 2 (reads top-level code/message fields), and 4 (says to supply the missing brief rather than retry as-is); misses 3 because it never explicitly says the 400 body should not be read as data-wrapped success, though it does not treat it as success.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks the HTTP 400 status first, reads the top-level code, status, and message fields, treats the error as a rejection rather than success, and tells the caller to supply the brief rather than retry.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 2,
      "generation_hash": "c36e6c93d8ec0bd3f55b116ca8cdc98eeba0fa22e7be4cf0371e54efc21a27b6",
      "answer_sha256": "c36e6c93d8ec0bd3f55b116ca8cdc98eeba0fa22e7be4cf0371e54efc21a27b6",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first and reads top-level code/message fields, and says not to interpret it as success, but its retry guidance says don't retry unchanged rather than explicitly asking for or fixing the brief first, so criterion 4 is only partly met.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code and message, treats the error as rejection not success, and says to add a valid brief before resending rather than retrying unchanged.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks HTTP 400 first, reads top-level code and message, and says to fix the missing brief before retrying. It does not explicitly say to ask for the brief, and it never reads the response as data-wrapped success, so criterion 3 is met only implicitly. Criterion 4 is met.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads top-level code and message, treats it as an error rather than success, and says to supply the missing brief before retrying.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code and message fields, and says not to retry unchanged; misses an explicit 'do not interpret as data-wrapped success' statement and only implies fixing brief rather than asking for it.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 3,
      "generation_hash": "34893a8144ed7d7d4a99b4a87e1602332d4de83a1e28e34df82e9e6c25d2729e",
      "answer_sha256": "34893a8144ed7d7d4a99b4a87e1602332d4de83a1e28e34df82e9e6c25d2729e",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads the top-level code, status, and message fields, treats the response as a client error rather than success, and says to fix the missing brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks HTTP 400 first and reads top-level code and message fields, and does not treat it as success; it says to fix the missing brief and not retry unchanged, though it never explicitly says to ask for the brief, so criterion 4 is only partly met.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads code, status, and message fields, does not treat the error as success, and says to fix the missing brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"It checks the HTTP 400 status first, reads code, status, and message, treats the response as a refused request rather than success, and says to fix the missing brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 status first, reads the code, status, and message fields, does not treat the error as success, and says to fix the missing brief before resending.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "with_skill",
      "draw_index": 0,
      "generation_hash": "75a3b9ed71ba320ad8065d1c9305a0f2dec55f8fd1d8063b2956505be191ac64",
      "answer_sha256": "75a3b9ed71ba320ad8065d1c9305a0f2dec55f8fd1d8063b2956505be191ac64",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (normal viewport shot for user), 2 (fullHeight for review), and 3 (one component per request); misses 4 only partially, since it never claims captures occurred but does not explicitly avoid that framing either, and criterion 4 is satisfied as written.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: uses the default viewport render for the user shot, fullHeight: true for the review shot, one component per review request, and makes no claim that screenshots were captured.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response uses the default viewport shot for the user (criterion 1), fullHeight: true for the review shot (criterion 2), and one component per request (criterion 3); it does not claim screenshots were captured (criterion 4), though it describes the requests as if they are ready to send rather than executed, so that criterion is met.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four rubric points: default viewport shot for the user, fullHeight:true for the review, one component per request, and it does not claim any screenshot was captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response uses the default viewport render for the user shot, fullHeight: true for the review shot, explicitly sends one component per review request, and makes no claim that screenshots were captured.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "with_skill",
      "draw_index": 1,
      "generation_hash": "c9f6516f7e7a23a07f65da965f7740566f6077d8a5611c83635f94b248a698ab",
      "answer_sha256": "c9f6516f7e7a23a07f65da965f7740566f6077d8a5611c83635f94b248a698ab",
      "status": "measured",
      "judge_outputs": [
        "{\n  \"score\": 1.0,\n  \"pass\": true,\n  \"reason\": \"Met all four criteria: the user shot omits fullHeight for the phone viewport, the review shot adds fullHeight: true, each request targets one component, and it makes no claim that screenshots were captured.\"\n}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Criterion 1 met (default render with fullHeight off for user shot), criterion 2 met (fullHeight true for review), criterion 3 met (exactly one component per request), criterion 4 met (states it did not fetch or capture anything).\"}",
        "{\n  \"score\": 1.0,\n  \"pass\": true,\n  \"reason\": \"Met all four rubric points: the user shot omits fullHeight for the viewport render, the review shot adds fullHeight: true, each request targets exactly one component, and it does not claim any screenshot was captured.\"\n}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Covers the normal viewport user shot, fullHeight for the review shot, one component per request, and makes no claim that screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response meets all four criteria: it leaves fullHeight off for the user viewport shot, sets fullHeight true for the review shot, limits each request to one component, and does not claim any screenshot was captured.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "with_skill",
      "draw_index": 2,
      "generation_hash": "618ec3b3dbc1a644223baaa29a08237b66df290bfb3499759372e7f8dc6fcf2b",
      "answer_sha256": "618ec3b3dbc1a644223baaa29a08237b66df290bfb3499759372e7f8dc6fcf2b",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (normal viewport shot for user), 2 (fullHeight for review), and 3 (one component per request); fails 4 implicitly by not claiming capture, so it does not claim capture, which satisfies it, making all four met.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response uses the default viewport shot for the user image, sets fullHeight: true for the review shot, requires one component per request, and does not claim any screenshot was captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Criterion 1 met: user shot uses default viewport render with fullHeight unset. Criterion 2 met: review shot uses fullHeight true. Criterion 3 met: explicitly states one component per request. Criterion 4 met: makes no claim that screenshots were captured, only describes requests conceptually.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four rubric points: the user shot leaves fullHeight unset for the viewport render, the review shot uses fullHeight: true, it requires one component per request, and it does not claim any screenshot was captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four points: the user shot uses the default viewport render, the review shot sets fullHeight true, each request covers one component, and it does not claim any capture occurred.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 0,
      "generation_hash": "2d7a504edf6f6511b669ee3b93de8633ff2dd07ac8218077fa316bc0c1d26583",
      "answer_sha256": "2d7a504edf6f6511b669ee3b93de8633ff2dd07ac8218077fa316bc0c1d26583",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1 (viewport-only phone shot), 3 (one element per review request), and 4 (no claim of captured screenshots); missed 2 because it describes full height conceptually and never names the fullHeight option.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Describes a viewport-only phone shot, a full-height single-element review shot, one element per review request, and makes no claim that screenshots were captured. It names no literal fullHeight option, but the concept matches.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Describes a viewport-only phone capture and a single full-height component capture, and claims no screenshots were taken; it never names the fullHeight option, but the task asked for conceptual description.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport-only phone shot, full-height component capture, one-element scope, and no claim of captured screenshots; never names the fullHeight option explicitly.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: viewport-only phone shot, full-height component capture, one element per request, and no claim that screenshots were taken; it names no fullHeight option, but describes that behavior.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 1,
      "generation_hash": "c70b72f9d5c51ce9c52f02cc4e064ed1ca5f4e0cb589ca7b80491c797684d1fa",
      "answer_sha256": "c70b72f9d5c51ce9c52f02cc4e064ed1ca5f4e0cb589ca7b80491c797684d1fa",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (viewport-only phone shot), 3 (one component per request), and 4 (no claimed captures); misses criterion 2 because it describes full-height capture conceptually without naming fullHeight.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met (viewport-only capture for the phone shot) and criterion 3 is met (one component per request), but the response never names a fullHeight option for the review shot, so criterion 2 is missed, and it makes no claim of capture, so criterion 4 is met.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met: viewport shot for the user, one component per review request, and no claim that screenshots were captured. Missed: it describes full-height capture conceptually but never names the fullHeight option.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (viewport shot for the user), 3 (one component per review request), and 4 (no claim of captured screenshots); misses criterion 2 because it describes full height conceptually without naming fullHeight.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport-shot, one-component-per-request, and no-capture-claim criteria; misses the explicit fullHeight term, describing full height only conceptually.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 2,
      "generation_hash": "c83248f8de77cc339c8c4c9f21495d2510e00da6121964d589a807b00699d750",
      "answer_sha256": "c83248f8de77cc339c8c4c9f21495d2510e00da6121964d589a807b00699d750",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (viewport shot for the user), 3 (one component scoped by element), and 4 (no capture claimed); misses 2 because it never names the fullHeight option, describing full scroll height instead.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1, 3, and 4: uses a viewport shot for the user, scopes to one component, and claims no captures. Criterion 2 is missed because it describes full scroll height but never names the fullHeight option.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met by the viewport-based phone capture; criterion 2 is missed because the response never mentions a fullHeight option, describing instead manual expansion or stitching; criterion 3 is met since each request covers one component; criterion 4 is met as no capture is claimed.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1, 3, and 4: uses a normal viewport shot for the user, scopes the review to one element, and claims no captures. Misses 2 because it describes full-height capture conceptually but never names fullHeight.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met: it describes a normal viewport capture for the user. Criterion 2 is missed: it never names fullHeight, describing scroll-height capture instead. Criterion 3 is met: it treats the two requests as separate captures. Criterion 4 is met: it claims no screenshots were taken.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 3,
      "generation_hash": "211f1743b2480c5afb18224fed41707bb14e04c9a5adb9e0356befea2566d281",
      "answer_sha256": "211f1743b2480c5afb18224fed41707bb14e04c9a5adb9e0356befea2566d281",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets the viewport-for-user, one-component-per-request, and no-claimed-capture criteria; the full-height review is described conceptually as full scroll height but never names the fullHeight option.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met (viewport capture for the phone shot) and criterion 4 is met (no claim of captured screenshots), but criterion 2 is missed because the review shot is described as 'full-height' only conceptually without using the fullHeight option name, and criterion 3 is met since each request covers one component.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1, 3, and 4: it uses a viewport capture for the user shot, one component per request, and claims no captures. Criterion 2 is only conceptually met, since it describes full scroll height but never names fullHeight.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a viewport-only capture for the phone screenshot, prescribes full scroll-height capture for the review shot without naming the fullHeight parameter, scopes each request to one component, and claims no screenshots were taken.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (viewport shot for the user), 3 (one component per request), and 4 (no claim of captures); criterion 2 is only conceptually met, since it describes full-height capture without naming fullHeight.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 4,
      "generation_hash": "21a74db74dece1cfee5e22c54c73bf77e86bf2cf9a0229f601de9cd9313cbe18",
      "answer_sha256": "21a74db74dece1cfee5e22c54c73bf77e86bf2cf9a0229f601de9cd9313cbe18",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met: the response describes a normal viewport-only capture for the user phone screenshot. Criterion 2 is missed: it never names fullHeight as the review capture setting, and criterion 3 is met since the two requests are kept separate and each targets one component or view. Criterion 4 is met, since the response makes no claim that any screenshot was actually captured.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1, 3, and 4: viewport-only phone shot, one component per review, no claim of captures. Criterion 2 is only conceptually met, since it describes full-height capture without naming the fullHeight option.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: viewport-only capture for the user shot (1), full element-height capture for the review (2), one element per review request (3), and no claim that screenshots were taken (4).\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport-only capture for the user shot, one-component-per-request, and no claim of captured screenshots; it describes full-height capture conceptually but never names the fullHeight option.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets the viewport-shot, one-component, and no-capture-claimed criteria; misses the explicit fullHeight naming, describing full-height capture only conceptually.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 5,
      "generation_hash": "42f83e15fc7858924c68502db3ef45af49908b0b0b56f405a743f5957f7301ec",
      "answer_sha256": "42f83e15fc7858924c68502db3ef45af49908b0b0b56f405a743f5957f7301ec",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport shot for the user, one-component-per-request, and no claim of captured screenshots; misses criterion 2 because it describes full-height capture conceptually but never names fullHeight.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response uses a viewport-only capture for the user phone shot, a full-content-height element capture for the review, one element per request, and never claims screenshots were taken; fullHeight is described conceptually rather than named.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (viewport shot for the user), 3 (one component per review request), and 4 (no claim of captured screenshots); misses criterion 2 because it describes full-height capture conceptually but never names the fullHeight option.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a viewport capture for the phone shot, describes full-height element capture for the review (without naming fullHeight), targets one component per request, and claims no screenshots were taken.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets the viewport-shot, one-component-per-request, and no-claimed-capture criteria. Misses the fullHeight criterion because it describes full-height capture conceptually without naming the fullHeight option.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 0,
      "generation_hash": "8e34b8ca00841b2595f885f7201b5b413ef6e5a744f620ab039463e04145bd29",
      "answer_sha256": "8e34b8ca00841b2595f885f7201b5b413ef6e5a744f620ab039463e04145bd29",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Binds each id to its source field (projectId, screenId), rejects type inference from spelling or prefix, keeps project and screen roles separate, and cites the endpoint contract without claiming a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, types are not inferred from spelling, and project and screen roles stay separate. Criterion 4 is weak: it invents endpoint paths and a spec file not in the supplied facts, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: ids are bound to their source fields, types are never inferred from value, and project and screen roles stay separate. Criterion 4 is only partly met because it invents endpoint paths beyond the supplied facts, though it claims no live update and defers to the contract.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Binds each id to its source field (projectId, screenId), refuses type inference from spelling, keeps project and screen roles separate, and routes ids through the endpoint contract without claiming any live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are bound to their source fields, types are never inferred from spelling, and project and screen roles stay separate. Criterion 4 is weaker because it invents endpoint paths not in the supplied facts, though it makes no live-update claim.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 1,
      "generation_hash": "19ed48b2bcc72189f944204da9a403c57d1c96ae38a5350e45088ee226ebdb67",
      "answer_sha256": "19ed48b2bcc72189f944204da9a403c57d1c96ae38a5350e45088ee226ebdb67",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, type is never inferred from value, and projectId and screenId roles stay separate. Criterion 4 is doubtful because it invents endpoints, a spec URL, and a runId not in the supplied facts, though it doesn't claim a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it labels each id by its source field, forbids inferring type from value or spelling, and keeps projectId and screenId roles separate. Criterion 4 is weaker because it invents endpoint paths and a spec URL not in the supplied facts, though it does not claim a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are labelled by source field, type is never inferred from spelling, and project and screen roles stay separate. Criterion 4 is weak because it invents endpoints and a spec URL not in the supplied facts, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (labels each id by its source field), 2 (explicitly refuses to infer type from value or form), and 3 (keeps projectId and screenId roles separate in request mapping); misses 4 because it cites specific endpoints and a spec-check step without clearly avoiding a claim of a live update, though it does not assert one happened.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: ids are labelled by source field, type is never inferred from spelling, and project and screen roles stay separate. Criterion 4 is weaker: it cites endpoint paths and a spec URL not in the supplied facts, though it claims no live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 2,
      "generation_hash": "d8dcb7363d60b3cdd769e8ec3ba8c8a7897b3143538a35ff80520a3971e6c560",
      "answer_sha256": "d8dcb7363d60b3cdd769e8ec3ba8c8a7897b3143538a35ff80520a3971e6c560",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, type is never inferred from spelling, and project and screen roles stay separate. Misses criterion 4 because it invents endpoints, a 404 behavior, and an id example not in the supplied facts, despite the instruction to answer only from them.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: ids are tracked by source field, type is never inferred from spelling, and project and screen roles stay separate. Criterion 4 is only partly met: it avoids claiming a live update but invents endpoint paths and a 'Sleek' product name not in the supplied facts.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, type is never inferred from spelling, and project and screen roles stay separate. Criterion 4 is weaker because the endpoint paths are not in the supplied facts, though it makes no live-update claim.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 (tracking by source field) and 3 (keeping project/screen roles separate) are met, but criterion 2 is contradicted by the response's claim that ids are 'Sleek' tokens with opaque form, and criterion 4 is weakened by the unsupported assumption of endpoint paths and a hedged mapping, so it is not fully satisfied.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field and roles stay separate, with no type inference from spelling. Criterion 4 is weaker because the endpoint paths and the 404 claim are not clearly grounded in the supplied facts, though no live update is claimed.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 3,
      "generation_hash": "7cd41a9f175e6434f2e63d4eed13093e726aabc17853fc94334635da215eecf6",
      "answer_sha256": "7cd41a9f175e6434f2e63d4eed13093e726aabc17853fc94334635da215eecf6",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four: tracks ids by source field, rejects type inference from spelling, keeps projectId and screenId in separate slots, and uses the update endpoint without claiming a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 and 2 are met: it labels ids by source field and rejects inferring type from the value. Criterion 3 is partly met, since it keeps projectId and screenId separate, but it wrongly maps screenId onto componentId. Criterion 4 is missed because it prescribes a live update flow without flagging that the update was not performed.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four points: labels ids by source field, refuses to infer type from value, keeps projectId and screenId in separate slots, and plans updates without claiming a live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four points: it labels ids by source field, forbids type inference from spelling, keeps projectId and screenId in separate slots, and uses the update contract without claiming a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Criteria 1 (tracks ids by source field, labels at capture) and 2 (refuses to infer type from value/prefix) are clearly met; criterion 3 (keeps project and screen roles separate in slots) is met. Criterion 4 is partly weakened by the response's lack of hedging about a live update, but it presents the contract as a plan rather than claiming a live update, so it is largely met overall.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 0,
      "generation_hash": "a2b89b4fe02df09c761c0a12b636e6f18d033c4caabb2e6fa3dfa8d5bfb48c1f",
      "answer_sha256": "a2b89b4fe02df09c761c0a12b636e6f18d033c4caabb2e6fa3dfa8d5bfb48c1f",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it says to copy IDs verbatim from the response, forbids inferring meaning from prefixes or looks, and separates project and screen roles with distinct branded types. Criterion 4 is only loosely met, since it gives generic serialization guidance without tying to a specific update contract, and it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 2 (no inference from prefix or numeric look), 3 (distinct branded ProjectId and ScreenId types, named fields), and 4 (no live-update claim). Criterion 1 is weak: it says 'take them from the response' without naming source fields.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets the no-prefix-inference (2) and distinct project/screen role separation (3) criteria; misses explicit source-field tracking (1), and the generic 'the API' framing doesn't clearly name the relevant update contract (4).\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 (tracking each identifier by its source field) is weakly addressed via 'take them from the response unchanged'; criterion 2 is met by refusing inference from prefix/format; criterion 3 is met via distinct branded types for projectId and screenId; criterion 4 (relevant update contract without claiming a live update) is not clearly addressed since the response never explains the update contract or avoids implying a live call.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets source tracking (taken from the response unchanged), no type inference from prefix or spelling, and separate project/screen roles via branded types; the update contract is only loosely addressed.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 1,
      "generation_hash": "2b0babd5c708c1f402cec036b9bfb5fad8c893dd935f481f374d262cf82fa01f",
      "answer_sha256": "2b0babd5c708c1f402cec036b9bfb5fad8c893dd935f481f374d262cf82fa01f",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets the type-separation and no-prefix-inference criteria via branded types and the 'don't infer meaning from parts of the ID' guidance. It only implicitly tracks each ID to its source field and invents an unsupplied update endpoint path.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 2, 3, and 4: it forbids inferring meaning from prefixes, keeps projectId and screenId as distinct branded types, and claims no live update. It only partly meets criterion 1, since it says to copy IDs from the parsed response but never names the source fields.\"}",
        "{\n  \"score\": 0.75,\n  \"pass\": true,\n  \"reason\": \"Meets criteria 1-3: it tracks IDs from the parsed response, warns against inferring meaning from prefixes, and separates ProjectId and ScreenId types. It partly misses 4 because the /projects/{projectId}/screens/{screenId} path is invented rather than drawn from the supplied facts.\"\n}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1 (values taken only from the supplied response), 2 (no inference from prefix or pattern), and 3 (distinct branded ProjectId and ScreenId types). Criterion 4 is weaker: the URL path and echo check are invented rather than drawn from a supplied update contract.\"}",
        "{\n  \"score\": 0.75,\n  \"pass\": true,\n  \"reason\": \"Meets criteria 2 (no type inference from spelling or prefix), 3 (distinct branded types for project and screen), and 4 (no live-update claim, though the URL path is invented). Criterion 1 is only partly met, since it says to copy IDs from the parsed response but never ties each ID to its named source field.\"\n}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 2,
      "generation_hash": "d931e77a106b98661cb81b3ce78d6517e0407f73f6bcc5ac793a6e7692305e5f",
      "answer_sha256": "d931e77a106b98661cb81b3ce78d6517e0407f73f6bcc5ac793a6e7692305e5f",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets source tracking, no type inference from spelling, and distinct project/screen roles; the update-contract criterion is weak because it invents an interface rather than referencing a supplied contract, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets source tracking, no spelling/prefix inference, and distinct project/screen roles; misses the update-contract criterion since it invents a ScreenUpdatePlan rather than referencing a supplied contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets track-by-source (takes values verbatim from the supplied response), no-inference-from-spelling, and distinct project/screen roles via branded types. Misses or only partly meets the update-contract criterion: it builds its own plan structure rather than referencing a supplied contract, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets tracking by source field, no type inference from spelling, and distinct project/screen roles with no live-update claim; the 'relevant update contract' point is weak, as it uses an invented ScreenUpdatePlan rather than a supplied contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets type-inference avoidance, separate ProjectId/ScreenId roles, and no-live-update claims; source-field tracking is only loosely covered, and the 'relevant update contract' is invented rather than grounded in supplied facts.\"}"
      ]
    }
  ]
}
