{
  "format": "driftproof-answers/2",
  "receipt_hash": "ea6c4a25fbedb8012f5ad2eb94eebdc68a77247a3183f9696d53d6c8940fd273",
  "model_id": "claude-sonnet-5-5",
  "answers": {
    "d73113105b6cc35994006025e57b51b5536a865091f726b0e8d792f3fb2553dc": "Read it as a request-validation failure: the request was rejected before any design work started, and the fix is on the caller's side.\n\n- **HTTP status first.** 400 means the server understood the request but refused it because its input was invalid. It is not an auth problem (401/403), a missing resource (404), a busy project (409), or a server fault (500).\n- **`message` says what to fix.** \"Missing brief\" means the request had no brief, the description of what to design. It was either absent, or sent under a different field name or in the wrong place in the body. The supplied facts don't say which field carries the brief, so check the request body contract in `endpoints.md` or the OpenAPI spec at `https://sleek.design/api/v1/spec.json` for the exact field name and location.\n- **`code` is `INVALID_INPUT`, not `BAD_REQUEST`.** The error table I have lists `BAD_REQUEST` for 400 validation failures, so this code doesn't match it. Branch on the HTTP status (400) and treat the code as informational. Don't write logic that only matches `BAD_REQUEST`, because the docs may be out of date or the code may vary by endpoint.\n- **No `data.url`.** There is no page where the user can resolve this, so there's nothing to relay. The caller has to correct the request.\n- **This is a top-level error, not a chat run error.** The run-level codes (`out_of_credits`, `execution_failed`, and so on) appear inside `data.error` of a run. This response has no run payload, so there is no run to poll or cancel.\n\nTo recover, add a non-empty brief in the right field, check that the body is valid JSON sent with `Content-Type: application/json`, and send it again. Retrying the identical request will fail the same way. Because the request failed validation, the corrected request is a new attempt rather than a replay of a run that started.",
    "79f24a1c685da32f3ab23134a025be94e73857ed49d795d8c50ca1c84078ef68": "**How to read it**\n\n- **HTTP status first:** `400` means the server rejected the request as invalid. It's a problem with what the caller sent, not an auth, plan, credit, or server issue. Sending the same request again unchanged will fail the same way.\n- **`message`: \"Missing brief\":** this is the actionable part. The request reached validation, but the brief (the description of what to design) was absent. It was probably omitted, misnamed, or empty or null in the JSON body.\n- **`code`: `INVALID_INPUT`:** the documented error table lists `BAD_REQUEST` as the code for 400 validation failures, and `INVALID_INPUT` isn't in it. So don't branch on the exact code string. Branch on the `400` status and use `message` for the specifics. The mismatch might mean the docs lag behind the API, but I can't confirm that from what was supplied.\n- **No `data.url`:** the error carries no link, so there's no page for the user to visit. The fix is in the request itself.\n\n**What the caller should do**\n\n1. Check the request body and make sure the brief is present, is a non-empty string, and uses the field name the endpoint expects. The exact name is in the endpoint reference or the OpenAPI spec at `/api/v1/spec.json`, which I haven't consulted here.\n2. Confirm the `Content-Type: application/json` header is set. Without it, the body may not be parsed and every field would look missing.\n3. Fix the payload and send it as a new request. A validation failure like this shouldn't have started a run, so there's likely no run to poll or cancel.",
    "f7d7787151540a5eb63f8ce23e1682d4e2b1452a670117e93e035f69720fc6b6": "**How to read it**\n\n- **HTTP status first.** The 400 means the request failed validation. The service rejected it before doing any work, so the request itself needs fixing. Retrying it unchanged will fail the same way.\n- **Error shape.** `code`, `status` and `message` at the top level match the documented error envelope. There is no `data` wrapper and no `data.url`, so there is no page to send the user to. This isn't an auth, plan, or credit problem.\n- **`message: \"Missing brief\"`.** This is the actionable part. The request carried no brief, meaning the description of what to design. It was either absent, or sent under a field name the endpoint doesn't read (for example, the wrong key or a malformed JSON body). I can't tell which from what you've given me.\n\n**One discrepancy**\n\nThe documented 400 code is `BAD_REQUEST` for validation failures. `INVALID_INPUT` isn't in the error table I was given. So:\n\n- Branch on the HTTP status (400) and the `message`, not on the exact `code` string.\n- Treat `INVALID_INPUT` as a validation-class error, equivalent in meaning to `BAD_REQUEST`.\n- Don't assume the table is exhaustive. The OpenAPI spec at `https://sleek.design/api/v1/spec.json` is the authority on the exact request field names and error codes.\n\n**What the caller should do**\n\n1. Check that the request body is valid JSON, sent with `Content-Type: application/json`.\n2. Check that the brief is present, non-empty, and under the field name the spec defines for that endpoint.\n3. Resend the corrected request as a new attempt. Nothing indicates a run was started, so there should be nothing to cancel or poll. Don't treat this as a replay of a failed run.\n\nI haven't contacted the service or checked the spec, so the exact field name to use is unconfirmed.",
    "c2e31b4467f6b9a9ca3fe5039b1d8de63d7cf3ceb150973ce35b8d4b34aa5690": "**How to read it**\n\n- **HTTP 400 is the signal that matters.** The docs say to check the HTTP status first. A 400 means the request failed validation: the server understood it, but it was malformed or incomplete. It is not an auth, plan, or server problem (those are 401, 403, and 500), and it is not a conflict with another active run (409).\n- **`message: \"Missing brief\"` is the actionable part.** The request did not include a brief, meaning the text describing what to design, or it was empty. The fix is on the caller's side.\n- **`code: INVALID_INPUT` doesn't match the documented table.** The error table lists `BAD_REQUEST` for 400 validation failures, and `INVALID_INPUT` isn't listed. Don't build logic that depends on that exact string. Branch on the HTTP status, and use `message` for the human-readable cause. The docs list the top-level fields as `code`, `status`, and `message`, with an optional `data.url`. No `data.url` was supplied here, which fits, since there is no page the user could visit to fix a missing field.\n\n**What the caller should do**\n\n1. Don't resend the same request, because it will fail the same way.\n2. Add a non-empty brief to the request body. Check the exact field name and where it belongs in `endpoints.md` or the OpenAPI spec at `https://sleek.design/api/v1/spec.json`. The facts supplied don't say which endpoint was called, so I can't name the field with certainty.\n3. Resend with the corrected body. A 400 validation rejection means the request was refused up front, so correcting and resending should be safe. It is not one of the run-level failures (`out_of_credits`, `execution_failed`) that the \"Safe retries\" guidance covers.\n4. If the brief is built from user input or a variable, check that it isn't empty, null, or whitespace-only before sending.",
    "8390a5505ebde25becfcc6900ec826aa76805c0b4703f8d1e3ff3aa98728ee9e": "**How to read it**\n\n- **HTTP 400 is the signal to act on.** The envelope rules say to check the HTTP status first. A 400 means the request failed validation, so the service rejected it before doing any work. This is a request-level error, not a chat run-level error. Those appear inside `data.error` on a run, with codes like `out_of_credits` or `execution_failed`.\n- **`message: \"Missing brief\"` is the actionable part.** The request had no brief, or it was empty. The brief is the design description you send to Sleek. It most likely belongs in the body of a chat message or project-creation call, though the supplied facts don't name the field.\n- **`code: INVALID_INPUT` doesn't match the documented table.** The skill documents 400 as `BAD_REQUEST`. Don't branch on the code string alone. Branch on the status (400) and show the `message`. The mismatch may mean the docs are out of date or this endpoint uses a different validation path. The OpenAPI spec at `https://sleek.design/api/v1/spec.json` would settle it.\n- **No `data.url` is expected.** The docs say only 401, 403 and 429 bodies may carry a fix-it link. A 400 has none, so the fix is in the request itself.\n\n**What the caller should do**\n\n1. Fix the request. Add a non-empty brief, and check that the field name and JSON structure match the endpoint's contract in `endpoints.md` or the spec. Also check that you sent `Content-Type: application/json`. A body the server can't parse can look like a missing field.\n2. Send it again as a corrected request. Retrying the identical payload will fail the same way.\n3. Don't poll or cancel anything. A validation failure at this stage means no run was started, so there is no run ID to follow.\n\nI haven't contacted the service, so this reading rests only on the facts you supplied and the documented error conventions.",
    "0b8f9897c7cf4873c0d8a7f16c53fedc550d1387cf71f03d9dd6fb4a20f26afb": "**How to read the response**\n\n- **HTTP 400 / `status: 400`**: This is a client error. The service received and parsed the request, but rejected it. The problem is with what the caller sent, not with the service being down or broken.\n- **`code: INVALID_INPUT`**: This is the machine-readable category. It means request validation failed, and callers should branch on this value rather than on the message text. It also tells you that retrying the identical request will fail the same way.\n- **`message: \"Missing brief\"`**: This is the human-readable detail. The request had no `brief` field, or the field was empty or null. `brief` appears to be a required input for this endpoint.\n\n**What the caller should do**\n\n1. Don't retry as-is. A 400 is deterministic, so resending the same payload won't help.\n2. Add the `brief` field to the request, with a non-empty value of the type the API expects. Typically that is a string describing what is being requested.\n3. Check that it is in the right place. A `brief` nested under the wrong key, misspelled, or sent with the wrong content type (for example, a JSON body without a `Content-Type: application/json` header) can look \"missing\" to the server.\n4. Resend once corrected.\n\n**Caveats**\n\nI only have the three facts you supplied, so I can't say what format `brief` must take, what its length limits are, or whether other fields are also required. The message names only the first failure found, so other validation errors may appear after this one is fixed. The service's API documentation would confirm the exact schema.",
    "c54754e87ad8ae89a46363a38741f689e1767a4fcf3b215a82be128daa992530": "**How to read it**\n\nThe response is a client-side validation error, not a service fault.\n\n- **HTTP 400**: the service understood the request but rejected it as malformed or incomplete. This is a \"Bad Request\" class error, meaning the problem is in what the caller sent, not in the server.\n- **`code: INVALID_INPUT`**: the machine-readable category. It tells the caller the failure is about the request's input, so branch on this value in code rather than parsing the message text.\n- **`status: 400`**: repeats the HTTP status in the body, so it should match the response's status line. It's useful if the body is logged or passed along without the headers.\n- **`message: \"Missing brief\"`**: the human-readable detail. The request has no `brief` field, or it was empty or null. I only know the name from the message; I don't have the Sleek API's schema, so I can't say where the field belongs (body, query, or elsewhere) or what format it expects.\n\n**What the caller should do**\n\n1. Check the request that was actually sent (not just the code that builds it) and confirm a `brief` is present, correctly named and spelled, in the location the API expects, and non-empty.\n2. Check for common causes: a variable that was undefined and dropped during serialization, a wrong field name or casing, or a wrong `Content-Type` that stopped the body from being parsed.\n3. Consult the Sleek API documentation for the exact definition of `brief`.\n4. Fix the request and resend it. Don't retry the same request unchanged, because a 400 of this kind is deterministic and will fail again. It also isn't a case for backoff or retry logic, which is meant for 5xx or rate-limit responses.\n\n**What it doesn't tell you**\n\nIt doesn't say whether other fields are also invalid. Some APIs report only the first problem found, so after adding `brief` you may see another `INVALID_INPUT` error for a different field.",
    "3621770de06c7d2fd3194af67a5085b7b24fd3e65f9c23a8139f916730b718b7": "**How to read it**\n\n- **HTTP 400** means a client-side error. The server understood the request but rejected it as malformed or incomplete. It isn't a server outage, an auth failure (that would be 401/403), or a missing route (404).\n- **`code: INVALID_INPUT`** is the machine-readable category. Branch on this in code rather than parsing the message. It says the request payload failed validation.\n- **`message: \"Missing brief\"`** is the human-readable detail. The request has no `brief` field, or it was empty or null. Based on the name, `brief` is presumably the required input describing what Sleek should work on. I only know it's required from this error, not from any documentation, so check the Sleek API docs for its exact type and format.\n- **`status: 400`** repeats the HTTP status in the body, so it should match the response status line.\n\n**What the caller should do**\n\n1. Don't retry the same request. A 400 is deterministic, and resending it unchanged will fail again.\n2. Check that the payload includes a `brief` field, spelled and cased exactly as the API expects, and at the location it expects (JSON body vs. query string).\n3. Check that it isn't an empty string, null, or whitespace only, and that it hasn't been dropped during serialization. Also confirm the `Content-Type` header (likely `application/json`) matches the body, since a mismatch can make a field look missing.\n4. Add the `brief`, then resend.\n\n**For error handling in general**: treat `INVALID_INPUT` as a non-retryable, caller-fixable error. Log the message and surface it to whoever builds the request, rather than applying backoff or retry logic.",
    "9004a160bd1ebb0d02536dc39674b9af75cf2528b922d88a71b681465893873b": "The response is a client-side validation error, not an outage or a transient failure.\n\n**Reading the fields**\n\n- **HTTP 400**: Bad Request. The server understood the request but rejected it because of what the caller sent. It is in the 4xx family, so the fault is on the caller's side.\n- **`code: INVALID_INPUT`**: A machine-readable category. It tells you the payload failed validation. Branch on this value in code rather than on the message text.\n- **`message: \"Missing brief\"`**: The human-readable detail. A required field named `brief` was absent from the request. Based on the wording alone, I can't tell whether it was omitted entirely, sent under a different key, or sent empty. The message says \"missing\", which suggests it wasn't present at all.\n\n**What the caller should do**\n\n1. Don't retry the same request unchanged. A 400 is deterministic, so repeating it will return the same error.\n2. Check the request body (or wherever the API expects `brief`) and confirm the field is present, spelled exactly `brief`, in the right location (JSON body vs. query string), and non-empty.\n3. Also check that the serialization and `Content-Type` are correct. A body that isn't parsed, such as a JSON payload sent without `application/json`, can make a present field look missing.\n4. Resend once `brief` is supplied.\n\n**Caveats**\n\nI only have the three facts you supplied. I don't know Sleek's schema, whether `brief` must be a string or an object, or its length limits, so check the API docs for the expected format. I also haven't contacted the service, so I can't confirm that adding `brief` is the only problem. Other validation errors could surface after this one is fixed.",
    "8b41c482be2b7b619b931ee2ca5868ffe4d46cef818423e8402f76d74c139ab7": "**How to read it**\n\n- **HTTP 400** means the service rejected the request as malformed or invalid. It's a client-side error: the request never got processed, and the problem is in what the caller sent, not in the service. Retrying the identical request will fail the same way.\n- **`code: INVALID_INPUT`** is the machine-readable category. It tells the caller that validation of the request payload failed, as opposed to an auth, rate-limit, or server problem. Callers should branch on this field rather than on the message text.\n- **`message: \"Missing brief\"`** is the human-readable detail. It says the request had no `brief`, which is a required input. Either the field was absent, or it was sent under a different name or in the wrong place (for example, in the query string instead of the body). It might also have been sent empty or null, depending on how the service validates it, though \"missing\" most directly suggests it was absent.\n\n**What the caller should do**\n\n1. Check the request that was sent and confirm it includes a `brief` field, spelled exactly that way, in the location the API expects (typically the JSON body).\n2. Make sure it has a non-empty value of the expected type.\n3. Fix the request and resend. Don't retry automatically with the same payload.\n\n**What it doesn't tell you**\n\nFrom the supplied facts alone, I can't say what format `brief` should take (string vs. object), whether other fields are also required, or whether the service reports multiple validation errors at once. If the payload has other problems, they may only show up after `brief` is fixed. The Sleek API documentation would confirm the expected schema.",
    "229f163df22444e50e4b468d25cbedc5a2796e264a76a0f1402487b7f96adb3b": "Based on the skill's definitions, here is how the two requests differ conceptually. I haven't looked at the exact request fields in endpoints.md, so I'm not naming specific parameters beyond what the skill states.\n\n**1. User shot (what you show the designer)**\n- It is the default render: viewport height, phone-shaped.\n- It goes through the screenshots endpoint (`POST /api/v1/screenshots`, scope `screenshots`) with no `fullHeight` flag.\n- Its purpose is presentation. A long component will appear cropped to the phone viewport, which is fine because this image is for the person looking at the design, not for judging completeness.\n\n**2. Review shot (what you judge your own work against)**\n- It is the same screenshots endpoint, but with `fullHeight: true`.\n- It is scoped to one component per request, so for a long component you ask for that component alone, not a batch.\n- The render captures the whole height of the component rather than just the first viewport. That lets you check the content below the fold: spacing, overflow, clipped elements, and whether the bottom sections match the brief.\n\n**How they fit together**\n- They are two separate requests. Neither replaces the other.\n- Show the user shot to the designer. Use the review shot to check your own work.\n- Both count toward the hourly render cap (1000 screenshots and share cards per org, API-key requests only), so there is no need to repeat them needlessly.\n- Both need `SLEEK_API_KEY` with the `screenshots` scope. Create the key with only the scopes the task uses.\n\nFor the exact request body (how the component is identified, plus any other fields), check endpoints.md or the OpenAPI spec before sending anything.",
    "2a4c6d540b6926e07c04f80311563d88dcca2eac0c4cea8a8a1a70ee1f515d9e": "Both requests go to the same endpoint, `POST /api/v1/screenshots` (scope `screenshots`), authenticated with the bearer key. They differ in framing and in what they're for.\n\n**1. User shot (what the designer sees)**\n- This is the default render: viewport height, phone-shaped.\n- It doesn't set `fullHeight`.\n- It's the one you show the user, so it answers \"what does this screen look like on a phone?\"\n- A long component gets cropped at the viewport edge, which is fine here because it's a presentation image and not an inspection tool.\n\n**2. Review shot (what you judge your own work against)**\n- Same endpoint, but with `fullHeight: true`.\n- One component per request, so a long component gets its own full-length render with nothing cut off.\n- This is for checking the whole component: spacing, content below the fold, alignment, and anything that overflows or gets clipped.\n- It's an internal check, not the image you hand the designer as the headline view.\n\n**How they fit together**\n- Send the user shot to the designer.\n- Send the review shot (one request per component) to verify the long component yourself.\n- If the review shot shows problems, fix them through another chat message, then re-render.\n\nI haven't made either request, since you asked for a conceptual description only. The exact body fields, such as how to identify the project and component, are in `endpoints.md`, which I haven't read here, so I won't guess at them.\n\nAlso worth knowing: screenshots count toward a render cap of 1000 screenshots plus share cards per hour per org on API-key requests, so avoid rendering needlessly in loops.",
    "9f74fa81426e6ebcf2b4f34f8dcfda05c36cf3bfb6335a13b14ae14d7e8bcb30": "Both requests go to the same endpoint, `POST /api/v1/screenshots`, with the same bearer auth and a key holding the `screenshots` scope. They differ in framing and purpose.\n\n**1. User shot (what the designer sees)**\n- This is the default render: viewport height and phone-shaped, so it looks like the screen on a device.\n- It needs no special framing option.\n- Show this image to the user as the presentable result of the design.\n\n**2. Review shot (what you judge your work against)**\n- This is the same kind of request with `fullHeight: true` added, so the long component renders at its full scrolling height instead of being cropped to the viewport.\n- Send one component per request. If several components are long, make a separate review request for each.\n- Use this image for your own checks, such as whether content further down the component is laid out correctly. It is not the image you present to the user.\n\nThe two are separate requests, not one request with two outputs. The user shot is for showing and the review shot is for checking. I haven't included exact body field names for identifying the project and component, because the supplied facts don't specify them. Those are in `endpoints.md` or the OpenAPI spec at `https://sleek.design/api/v1/spec.json`.\n\nScreenshots count toward the render cap of 1000 screenshots and share cards per hour per org, so avoid sending extra review requests without a reason.",
    "bf47b66373f301f48d3f92b4b283fba3a301a6db18644ba0892d6070d848775d": "Here are the two screenshot requests, described conceptually. I haven't run anything.\n\n## 1. User-facing phone screenshot\n\n**Purpose:** Show what a real person sees on their phone, as it would appear in a product page, store listing, or presentation.\n\n- **Viewport:** A typical modern phone portrait size, for example around 390×844 CSS pixels, at 2x or 3x device pixel ratio for sharpness.\n- **Capture area:** Only the visible viewport (above the fold), not the whole page. The crop is what the user would see on first load or at a chosen scroll position.\n- **Emulation:** Mobile user agent and touch input, so responsive layouts, mobile navigation, and tap-sized controls render as they do on a device.\n- **Content state:** Polished and representative: realistic data, no debug overlays, no loading spinners, no dev toolbars, and animations settled.\n- **Optional framing:** A device bezel or status bar can be added afterward for marketing use. That is presentation, not part of the capture.\n- **Output:** A single image sized for the viewport. It communicates look and feel, not completeness.\n\n## 2. Separate full-height review of a long component\n\n**Purpose:** Let the designer inspect the entire component from top to bottom in one image, to check spacing, alignment, hierarchy, and consistency throughout.\n\n- **Viewport:** Same width as the phone capture (so the layout matches), but the height extends to the component's full scroll height.\n- **Capture area:** The full component, or the full page if the component can't be isolated. Ideally the capture is scoped to the component's bounding box so surrounding chrome doesn't intrude.\n- **Content state:** Everything expanded and rendered, including lazy-loaded images, collapsed sections, and long lists, so nothing is missing from the lower parts.\n- **Fixed elements:** Sticky headers, floating buttons, and cookie banners can repeat or overlap in tall captures, so they should be hidden or accounted for.\n- **Output:** One tall image, possibly split into segments if it exceeds image size limits. Annotations such as measurements or notes can be layered on during review.\n\n## Key differences\n\n| | Phone screenshot | Full-height review |\n|---|---|---|\n| Audience | End users, marketing | Designer, internal review |\n| Height | Viewport only | Entire component |\n| Goal | Show the experience | Verify the details |\n| State | Curated, polished | Fully expanded, complete |\n| Extras | Optional device frame | Optional annotations |\n\nThey are separate requests because they answer different questions. The first shows how it feels, and the second checks whether the whole thing is built correctly.",
    "d6aed3d3997353608ac328cd822d44214c6fa11e5403389ba93744aaaf127a30": "Here are the two requests, described conceptually. I haven't run anything, so these describe intent, not output.\n\n**1. User-facing phone screenshot**\n- **Purpose:** show the designer what a real person sees on a phone on first load.\n- **Viewport:** a typical phone-sized portrait viewport (narrow width, tall-ish height), with a mobile device scale factor and mobile user agent/touch emulation if available.\n- **Capture mode:** viewport only, not the full page. Anything below the fold is cropped, as it would be on a real device.\n- **Content state:** the page in its normal, as-delivered state, with no scrolling, expanded sections, or forced overrides, so it reflects the actual user experience.\n- **What the designer learns:** first-impression layout, above-the-fold hierarchy, tap-target sizing, text legibility, and how the component behaves at narrow width.\n\n**2. Separate full-height review of the long component**\n- **Purpose:** let the designer inspect the entire component from top to bottom in one image, without scrolling or stitching.\n- **Target:** the long component itself, either by selecting that element or by capturing the full scrollable page height, rather than just the visible window.\n- **Viewport width:** can match the phone width if the question is how the component lays out on mobile, or be a wider desktop width if the review is about the component generally. The designer should state which.\n- **Capture mode:** full-height/full-page, so the image is as tall as the content requires.\n- **Content state:** any lazy-loaded or collapsed content should be loaded or expanded first, so nothing is missing from the review.\n- **What the designer learns:** vertical rhythm, spacing consistency, section ordering, truncation or overflow problems, and how the end of the component looks.\n\n**Why keep them separate**\n- The phone shot is a faithful, cropped representation of the user's experience, while the full-height shot is an inspection artifact that shows more than a user would see at once.\n- Combining them would either distort the realism of the phone view or lose detail in the long view.\n- Each should be labeled clearly (for example, \"phone viewport, as seen by user\" vs. \"full-height component review\") so they aren't mistaken for one another.",
    "afacd093176e7eb185c6894f06a89715891a6ac7aa52a54a5af87950e3f50144": "Here are the two requests, described conceptually. Nothing has been run.\n\n**1. User-facing phone screenshot**\n\n- **Purpose:** show what a real user would see on their device, as in a marketing page, a store listing, or a stakeholder preview.\n- **Viewport:** a fixed phone-sized frame (a narrow width, a tall-but-bounded height, and a high device pixel ratio so text stays crisp).\n- **Capture area:** only what fits in that viewport, i.e. \"above the fold.\" Anything that would scroll is cropped, as it would be on the actual device.\n- **Emulation:** mobile user agent, touch input, and the mobile layout breakpoint. This triggers the responsive styles a phone would get.\n- **Content state:** polished and representative, with realistic sample data, no debug overlays, and fonts and images fully loaded.\n- **Framing:** optionally wrapped in a device bezel or status bar, since this one is presentational.\n\n**2. Separate full-height review of the long component**\n\n- **Purpose:** let the designer inspect the entire component top to bottom for spacing, alignment, hierarchy, and consistency, without scrolling or stitching images together.\n- **Viewport:** the width can match the phone (or whatever target the component is designed for), but the height is not fixed. The capture extends to the component's full scroll height.\n- **Capture area:** ideally the component itself, rather than the whole page, so surrounding chrome doesn't add noise. If the component has its own internal scroll container, that container needs to be expanded or the capture needs to account for it. Otherwise the hidden overflow stays hidden.\n- **Content state:** it should show the longest realistic content, with all sections expanded, so nothing is collapsed or lazy-loaded out of view. Lazy-loaded content needs to be triggered first.\n- **Framing:** no bezel or decoration, since this one is for critique. Neutral background and no cropping.\n\n**Why keep them separate**\n\nThe phone shot is about *presentation* (what fits on screen, looking its best). The full-height shot is about *inspection* (completeness, accuracy). Combining them would force a trade-off: either the phone shot gets stretched and loses its device realism, or the review shot gets truncated and hides the lower part of the component.",
    "09820fd71860d2776c9383a8b07f1cd2e21712646e3575b5016206622b42b609": "Here are the two screenshot requests, described conceptually. Nothing has been run or captured.\n\n**1. User-facing phone screenshot**\n- **Purpose:** Show what a real person sees on their device, for marketing, docs, or a design presentation.\n- **Viewport:** A typical phone-sized screen (narrow width, tall aspect ratio, high pixel density), in portrait orientation.\n- **Capture scope:** Only the visible viewport. Content below the fold is cut off, as it would be on the actual device.\n- **Content state:** Realistic, polished data, with no debug overlays, dev tools, or placeholder text. The component should be in its intended resting state, scrolled to the most representative position.\n- **Framing:** Optionally placed inside a device frame or with a clean background. The focus is on how it looks and feels in use.\n\n**2. Full-height review of the long component**\n- **Purpose:** Let the designer inspect the entire component from top to bottom in one image, checking spacing, alignment, hierarchy, and consistency.\n- **Viewport:** The same phone width, so the layout matches what users get, but with the height extended to cover the component's full scroll length.\n- **Capture scope:** The whole component end to end, with no cropping at the fold. This is a full-page or element-level capture rather than a single screen.\n- **Content state:** Worth including the longer or edge-case content (long text, many items, empty or error sections) so the designer can spot overflow, truncation, or rhythm problems.\n- **Framing:** Plain and unframed, with no device chrome, since the image is a working artifact rather than a presentation piece. Annotations or measurement overlays are fine here.\n\n**Key difference:** The first image is a curated, viewport-bound view of the experience. The second is a complete, full-length view of the structure. Both should share the same width so the designer can compare them directly.",
    "87e057c7de77c6070f4b81386735138686ced2d9975f89691dc391f078238b9b": "Here are the two screenshot requests, described conceptually. Nothing has been run or captured.\n\n**1. User-facing phone screenshot**\n\n- **Purpose:** Show what a real person sees on their phone, for sharing, presentation, or a quick visual check of the mobile experience.\n- **Viewport:** A typical phone-sized screen, in portrait, with a device pixel ratio that matches modern phones so text and edges look crisp.\n- **Capture area:** Only the visible viewport, the \"above the fold\" view, not the whole page. Anything below the fold is cropped out, as it would be on a real device.\n- **State:** The component in its default, realistic state, with representative content, no dev overlays, and no debug chrome. If the screenshot is for users, it should look polished and typical rather than edge-case.\n- **Output:** One image with phone proportions, easy to drop into a deck, doc, or message.\n\n**2. Full-height review of the long component**\n\n- **Purpose:** Let the designer inspect the whole component from top to bottom in one image, checking spacing, alignment, hierarchy, and consistency across its entire length.\n- **Viewport:** The width should stay fixed, ideally matching the phone width above so the layout is comparable, while the height extends to cover the component's full scroll height.\n- **Capture area:** The full component, or the full page if the component spans it, rather than just what fits on one screen. Target the component's own bounds if possible, so surrounding page content doesn't add noise.\n- **State:** Everything should be fully rendered before capture, including lazy-loaded images, animations that have settled, and any expandable sections the designer wants to review. Sticky or fixed elements like headers and floating buttons can appear repeated or misplaced in a stitched capture, so they should be accounted for.\n- **Output:** One tall image, which may be very large. If it gets unwieldy, splitting it into consecutive sections with slight overlap is a reasonable alternative.\n\n**Key differences**\n\n| | Phone screenshot | Full-height review |\n|---|---|---|\n| Audience | End users, presentations | Designer, internal review |\n| Height | Viewport only | Entire component |\n| Goal | Realistic first impression | Complete inspection |\n| Risk | Cropping important content | Sticky elements, lazy content, huge file size |\n\nThey should be treated as two separate captures because they serve different goals. Cropping to the viewport would hide most of the long component, while a full-height image wouldn't show what a user sees on first load.",
    "2b765a29ac52d609126937c9b7a4e213efc45c87afd5978b8838a77bca748cbb": "Sleek ids are opaque 11-character tokens with no type prefix, so `V1StGXR8Z5j` could be a project, screen, component, version or theme. The id itself tells you nothing, and the only reliable way to keep types straight is to record them yourself at the moment they arrive.\n\n**Track by the response field the id came from**\n\n- When you parse a response, store each id under the field name it was delivered in: `projectId` goes in a `projectId` slot, `screenId` in a `screenId` slot. Never keep a bare list of ids.\n- Keep them in a small labeled structure, for example:\n  ```\n  project: { id: <projectId> }\n  screens: { <screenId>: { projectId: <projectId>, source: \"run response\" } }\n  ```\n  Every screen entry carries the project it belongs to, so a screen id is never separated from its parent.\n- Do not guess a type from length, shape, or position. Two ids with the same format can be completely different resources.\n\n**When planning a screen update**\n\n1. Write the plan with the labels attached: \"Update screen `screenId=<value>` in project `projectId=<value>`\", not \"update `<value>`\".\n2. Use `projectId` wherever the API wants a project, such as the `:id` in `/api/v1/projects/:id/chat/messages`. Use `screenId` only where a screen is called for. Because the types are interchangeable on the surface, a swapped value can still look valid and fail as `NOT_FOUND` or, worse, hit the wrong resource.\n3. Chat messages are scoped to a project. The update is requested through the project's chat, and the screen is identified in the message content, so the `screenId` stays attached to its project in your notes rather than being used as a path parameter.\n4. Before sending, do a quick check of each id against its label: the one in the URL path should be the one from `projectId`, and the one referenced as the target screen should be the one from `screenId`.\n\n**If the source is ambiguous**\n\nIf you ever hold an id and cannot say which field it came from, don't reuse it. Re-fetch from the originating response, or from a listing such as the project's components, and re-label it from there.",
    "1d654e9fc31b5c7553216de7c9a218405f9564931f15010a9a31e0dba54472d4": "Sleek ids carry no type information: both `projectId` and `screenId` are opaque 11-character tokens like `V1StGXR8Z5j`, with no prefix or format difference. You can't tell what an id names by looking at it, so the type has to be tracked by where it came from.\n\n**How to retain the types when planning a screen update**\n\n1. **Record each id by the response field it arrived in.** The moment you read the response, write down a labeled pair, for example:\n   - `projectId = <value from the projectId field>`\n   - `screenId = <value from the screenId field>`\n\n   Never keep a bare list of ids, and never infer the type from position, length, or appearance.\n\n2. **Keep the labels attached when you reuse them.** Any later step, such as building a request path or a chat message, should reference the id by its label (`projectId`, `screenId`), not by pasting the raw token with no context. Project-scoped endpoints take the project id in the `:id` path segment, so only `projectId` belongs there.\n\n3. **Refer to the screen by `screenId` in the update brief.** Chat messages go to the project (`POST /api/v1/projects/:id/chat/messages`), so the project id addresses the request while the screen id identifies which screen the instruction is about. Keep these roles separate in your plan.\n\n4. **Don't mix in other id kinds.** Components, versions, themes, and runs also use the same token shape. If the same response or a later one contains a `runId`, `componentId`, or version id, label each of those separately too.\n\n5. **If a label is lost, re-derive it rather than guess.** Because an id can't be classified by inspection, a wrong guess (say, a screen id placed in the project path) would likely produce a `404 NOT_FOUND` or hit the wrong resource. Go back to the response field the value came from, or re-fetch the project or component listing to recover the mapping.\n\n**Plan sketch:** hold `projectId` and `screenId` as a labeled pair, use `projectId` for the request target, name `screenId` as the subject of the requested change, and verify afterward against the same labeled pair.",
    "97ab5af7ff84adc21015c01e514e066cca958aeb2e0a537224c4371cc4f9c060": "Sleek ids are opaque 11-character tokens with no type prefix, so nothing in `V1StGXR8Z5j` tells you whether it names a project, screen, component, version or theme. The type lives only in the response field the id arrived in. To keep the types straight when planning a screen update:\n\n1. **Bind each id to its field at the moment you read it.** Record them as labeled pairs, not as bare strings:\n   - `projectId = <value from the projectId field>`\n   - `screenId = <value from the screenId field>`\n\n   Never store a list like \"ids: abc..., def...\" because the labels are the only thing that distinguishes them.\n\n2. **Never infer type from shape or position.** Both are 11 characters, so length, charset, or the order you saw them in proves nothing. Don't assume \"the first one is the project.\"\n\n3. **Carry the label through the plan.** Write each planned step with the role named explicitly, for example: \"send the chat message to `/api/v1/projects/{projectId}/chat/messages`, referencing the target screen via `screenId`.\" Place each id only in the slot its label matches. Putting a screen id in a `:id` project path segment would not be caught by format validation, and would most likely come back as `404 NOT_FOUND` or, worse, target the wrong resource.\n\n4. **Keep related ids separate even when they're easy to confuse.** A component id, version id and theme id are also 11-character tokens. If a later response gives you those, store them under their own field names, not alongside or in place of the project and screen ids.\n\n5. **Re-check against the source field when unsure.** If a label gets lost, go back to the original response and re-read which field the value came from, rather than guessing.\n\nIn short: treat an id as a (field name, value) pair, never as a bare value, and let the field name decide where it can be used.",
    "44ddfd0f902cd21890c3bbbd0e8f700684da60e7b060684bd86dfbac32506b34": "Sleek ids are opaque 11-character tokens with no type prefix, so `V1StGXR8Z5j` could be a project, screen, component, version, or theme id. Nothing in the value tells you which. The type lives only in the response field the id arrived in, so you have to carry that yourself.\n\n**How to retain the types when planning a screen update**\n\n1. **Bind each id to its field name at the moment you read it.** When you parse the response, record them under explicit labels rather than as bare strings:\n   - `projectId = data.projectId`\n   - `screenId = data.screenId`\n\n   Keep those labels through the whole plan. Don't collapse them into a generic `id` or an unlabeled list like `[\"V1StGXR8Z5j\", \"k9Qw2LmPz0A\"]`.\n\n2. **Write the plan with the role next to each value.** For example:\n   - Project: `projectId=<value>` (from the run response's `projectId` field)\n   - Screen to update: `screenId=<value>` (from the run response's `screenId` field)\n\n   If you later paste an id into a URL path, a request body, or a note to the user, name its role beside it so it can't be mistaken for another kind.\n\n3. **Place each id only where its type belongs.** Project ids go in `/api/v1/projects/:id/...` paths. A screen id is not a substitute for a project id there, even though both look identical and the API may not reject the swap in an obvious way. A wrong-type id will most likely surface as a `404 NOT_FOUND` or as an operation on the wrong resource, which is hard to diagnose after the fact.\n\n4. **Don't infer type from shape, position, or order.** Both values are 11 characters of the same alphabet. Don't guess which is which from length, from the order they appear in the JSON, or from similarity to an earlier id in the conversation. If provenance is lost (say, you only have two bare strings), go back to the original response to re-establish which field each came from, rather than guessing.\n\n5. **Refer to the screen in the chat message by intent, not just by id.** When you plan the update, state in the message which screen you mean in plain language (its name or purpose) alongside tracking `screenId` yourself. That gives a second check that the right screen is being targeted.\n\n6. **Verify before acting when it matters.** If the plan involves anything hard to undo, re-read the labeled values back against the supplied response before you send the request, confirming that the value under `projectId` is the one used for the project and the value under `screenId` is the one used for the screen.\n\nIn short: the type is carried by the field name, not the token, so keep the field name attached to the value in every step of the plan.",
    "2d002f3cbfe11e992fe85c82e9918f568680da50dd8aa269975016bf008ce5f5": "Treat both IDs as opaque, exact-match strings and carry them through the plan unchanged.\n\n**Principles**\n\n1. **Keep them as strings end to end.** Never convert to a number, even if a value looks numeric. That can drop leading zeros, use exponent notation, or lose precision.\n2. **Don't transform them.** No trimming, case-folding, truncating, padding, re-encoding, or splitting on separators. Copy the value from the response into the plan verbatim.\n3. **Don't infer meaning from the contents.** Don't derive one ID from the other, assume a prefix means something, or guess a missing one. If an ID isn't in the supplied response, the plan should say it's missing rather than fabricate it.\n4. **Keep the two IDs distinct.** They have the same shape (11 characters), so nothing but your naming will stop you from swapping them. Always use the full field names `projectId` and `screenId`, never generic names like `id`.\n5. **Validate only at the boundary.** At most, check that each is a string of length 11. Don't validate beyond that, since the character set is unspecified.\n\n**Making the types explicit in the plan**\n\nIf you're writing typed code, use nominal (branded) types so the compiler rejects a swap:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\nfunction asProjectId(s: string): ProjectId {\n  if (s.length !== 11) throw new Error(\"projectId must be 11 chars\");\n  return s as ProjectId;\n}\nfunction asScreenId(s: string): ScreenId {\n  if (s.length !== 11) throw new Error(\"screenId must be 11 chars\");\n  return s as ScreenId;\n}\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: unknown; // whatever the update contains\n}\n```\n\nIn a non-typed plan (a checklist or JSON), state it in the schema instead: both fields are `\"type\": \"string\"`, `\"minLength\": 11`, `\"maxLength\": 11`, and are copied from the response without modification.\n\n**Checks before executing the plan**\n\n- Both values in the plan match the response character for character.\n- `projectId` is only used where a project is expected, and `screenId` only where a screen is expected.\n- Any serialization step (JSON, URL, logs) keeps them as quoted strings and doesn't re-encode them.",
    "a9537346c69635f2450de57c0d395dd94a032eca0b52daac6f28e4c743dbfdb9": "# Retaining `projectId` and `screenId` types in a screen-update plan\n\nBoth are opaque 11-character strings, so the main risks are mangling them and swapping them. Their identical shape means nothing will catch a mix-up for you.\n\n## 1. Treat them as opaque strings\n- Copy each value verbatim from the response. Don't trim, re-case, truncate, pad, or reformat.\n- Don't parse them or infer meaning from their characters, such as a prefix, an encoding, or sortability.\n- Don't coerce them to numbers. An ID that looks numeric, or like `1e5`, can lose leading zeros or change value.\n- Don't compare them with each other. Equality is only meaningful between two IDs of the same kind.\n\n## 2. Keep the two kinds distinct\nSince both are 11-character strings, the type system can't tell them apart unless you make it. In a typed language, use branded or newtype wrappers:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\nfunction planScreenUpdate(projectId: ProjectId, screenId: ScreenId, changes: ScreenChanges) { /* ... */ }\n```\n\nWithout static types, keep them under explicit, consistent field names (`projectId`, `screenId`) and never use a generic `id`.\n\n## 3. Validate only at the boundary\n- When you receive the response, check that each value is a string of length 11. Reject anything else instead of \"fixing\" it.\n- After that, pass the values through unchanged.\n\n## 4. State them explicitly in the plan\n- Each plan step that touches a screen should name both IDs and say which is which, for example: \"Update screen `<screenId>` in project `<projectId>`.\"\n- Keep the project-to-screen pairing. A `screenId` is only safe to use alongside the `projectId` it came from.\n- Don't reconstruct IDs from memory or from earlier turns. Reference the values from the supplied response.\n\n## 5. Serialize carefully\n- Use the same string representation in JSON, URLs, and logs, and encode only where the transport requires it, without altering the stored value.\n- Make argument order explicit. Prefer a named-field object like `{ projectId, screenId }` over positional arguments.",
    "a8e7f3c39168c3f7080ea8fa539cf0f64e2f4a72b5985b5e2152a1627f160e12": "Treat both IDs as opaque strings and carry them through the plan exactly as received.\n\n**1. Keep them as strings end to end**\n- Don't coerce them to numbers, even if one happens to look numeric (for example, an all-digit value or one in scientific notation like `12345e12`). Coercion can drop leading zeros or change the value.\n- Don't trim, lowercase, URL-decode, or otherwise normalize them. Case and characters may be significant.\n- Don't parse, split, or infer meaning from their contents, or assume that a shared prefix means the same project.\n\n**2. Make `projectId` and `screenId` distinct types**\nBoth are 11-character strings, so the compiler or schema can't tell them apart unless you make it. In TypeScript, use branded types:\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: ScreenChange[];\n}\n```\n\nIn Python, use `NewType(\"ProjectId\", str)` and `NewType(\"ScreenId\", str)`. In a JSON schema, use separate named fields with `\"type\": \"string\"` and `minLength`/`maxLength` of 11, never a positional array or a generic `id`.\n\n**3. Validate at the boundary only**\nWhen you receive the response, check that each field is a string of length 11. Reject or flag anything else, then wrap it in the branded type. Don't validate beyond that, since you don't know the character set or structure.\n\n**4. Pass through verbatim**\nCopy the values from the response straight into the plan, tool call, or request. Don't retype them or reconstruct them from memory or from a summary. A single character slip produces a plausible but wrong ID.\n\n**5. Keep the pairing explicit**\nA screen belongs to a project, so keep `projectId` and `screenId` together in one object, and name the fields in full every time. Don't call either one `id`. If the plan covers multiple screens, make each entry its own `{projectId, screenId}` pair so they can't drift apart or be swapped.\n\n**6. Compare only by exact equality**\nUse strict string equality. Use them as map keys as-is, and key a composite lookup on the pair (for example, a nested map `projectId → screenId`) rather than a concatenated string.\n\n**7. Log and display carefully**\nPrint them in full, quoted if helpful, and don't truncate. If you need to label them in a plan, write something like `projectId: \"…\"` and `screenId: \"…\"` so the type is obvious to a reader.",
    "c6aabf65c056c1ca4d186cb1a6be07e0dbc5db68eadb39629ccbbe735e2c525d": "Treat both IDs as opaque, distinct, immutable values: carry them through the plan exactly as received, and never let one stand in for the other.\n\n**1. Keep them as strings, verbatim**\n- Don't trim, re-case, truncate, pad, or reformat them, and don't coerce them to numbers. An 11-character ID can look numeric or contain leading zeros, and conversion would corrupt it.\n- Don't parse them or infer meaning from their characters, prefixes, or position. \"Opaque\" means the only valid operations are equality comparison and passing them along.\n\n**2. Give each its own type**\nAt the language level, make `ProjectId` and `ScreenId` separate types so they can't be swapped by accident. In TypeScript, for example:\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: ScreenChange[];\n}\n```\n\nOther languages can use newtypes or wrapper classes. If you have no type system, use clearly named fields (`projectId`, `screenId`) and never a generic `id`.\n\n**3. Validate once, at the boundary**\nWhen you receive the response, check that each ID is a string of exactly 11 characters, then wrap it in its type. After that, internal code should trust the types and not re-validate or re-derive them.\n\n**4. Keep them paired and named in the plan**\n- Store both IDs in the plan object, side by side, under their own names. A screen update targets a specific screen within a specific project, so don't drop `projectId` once you have `screenId`.\n- Don't assume `screenId` is unique across projects unless the facts say so. Use the pair as the key.\n- Don't build composite strings like `\"${projectId}/${screenId}\"` as a substitute for the structured pair. If you need a map key, derive it separately and keep the original typed fields.\n\n**5. Pass them through unchanged to later steps**\nAny later call, such as an update request, should take the typed values from the plan and not re-extract them from text or logs. Compare them to the originals by exact equality before acting, so a mix-up fails loudly instead of updating the wrong screen.\n\n**6. Guard against swaps**\nAdd a check, or a test, that rejects a plan whose `projectId` was supplied where `screenId` belongs. Since both are 11-character strings, length checks can't catch this, so the type separation is your main protection.",
    "aba99428f972613ec9efd3ed3a8128831f3750d34f6dc296b7d67614db1050b9": "Treat both values as opaque, distinct, immutable strings. Carry them through the plan exactly as received, and never interpret them.\n\n**1. Keep them as strings, verbatim**\n- Don't parse, split, trim, re-case, pad, truncate, or coerce them (for example to numbers, even if one looks numeric or has a leading zero or `e`).\n- Don't infer structure or meaning from the characters, and don't derive one ID from the other.\n- Copy them character for character into every step of the plan that needs them.\n\n**2. Give each its own type so they can't be swapped**\nBoth are 11-character strings, so a plain `string` type lets `projectId` and `screenId` be passed in the wrong order without any error. Use nominal (branded) types:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\nfunction asProjectId(s: string): ProjectId {\n  if (s.length !== 11) throw new Error(\"projectId must be 11 chars\");\n  return s as ProjectId;\n}\nfunction asScreenId(s: string): ScreenId {\n  if (s.length !== 11) throw new Error(\"screenId must be 11 chars\");\n  return s as ScreenId;\n}\n\nfunction planScreenUpdate(projectId: ProjectId, screenId: ScreenId, changes: ScreenChanges) { /* ... */ }\n```\n\nThe same idea works in other languages: a newtype in Rust, a distinct wrapper class in Java or Kotlin, or `NewType` in Python.\n\n**3. Validate only at the boundary**\n- Check the length (11) once, where the response is read. Don't add character-set rules, because \"opaque\" means you don't know the format.\n- After that, pass the typed values around without re-validating or re-wrapping them.\n\n**4. Keep them paired in the plan**\n- Have the plan's update step take both IDs as named fields (`{ projectId, screenId, ... }`), not positional arguments or a combined string like `\"proj/screen\"`.\n- A screen ID is only meaningful within its project, so keep them together. Don't concatenate them into one key unless you keep the original fields too.\n\n**5. Serialize without loss**\n- In JSON, emit them as strings. Compare them only by exact equality.\n- Don't log a normalized form in place of the real value, since that's the form that would end up in later calls.",
    "897dde6f8e50782583f12cc4fb019eba399af04f39d4b150d09fccb63d38a817": "Treat both IDs as opaque, distinct values and pass them through unchanged.\n\n**1. Keep them as strings, exactly as received.**\n- Don't parse, trim, re-case, truncate, pad, or coerce them (for example to numbers, even if one looks numeric).\n- Don't derive meaning from their characters. Only the service that issued them knows their structure.\n- Copy them verbatim into the update plan. Don't retype or paraphrase them.\n\n**2. Keep them as two different types.**\nBoth are 11-character strings, so the compiler or reader can't tell them apart unless you make them distinct. Use branded (nominal) types:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\n// Only construct at the boundary where the response is read.\nconst asProjectId = (s: string) => s as ProjectId;\nconst asScreenId  = (s: string) => s as ScreenId;\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: /* ... */ unknown;\n}\n```\n\nThis makes `updateScreen(screenId, projectId)` a type error instead of a silent bug.\n\n**3. Brand once, at the boundary.**\n- Convert the raw response fields into `ProjectId` and `ScreenId` as soon as you read them, and use only the branded types afterward.\n- Optionally check at that point that the length is 11, but don't normalize anything. If the check fails, report the error rather than fixing the value.\n\n**4. Keep the field names explicit through the plan.**\n- Carry them as separate named fields (`projectId`, `screenId`), not as a positional pair or a concatenated key like `\"proj/screen\"`.\n- If you need a composite key (a cache or map key), build it from both values with an unambiguous structure, such as a tuple or nested map, and don't parse it back apart later.\n\n**5. Don't let them leak into other roles.**\n- Don't use them as display names or labels, and don't embed them in generated text that gets transformed.\n- If you log them, log them unmodified.\n\nFollowing these rules means the plan targets exactly the project and screen the response named, and mixing the two up fails at compile time or review time instead of at runtime.",
    "52c1f2bf854024529728f646fa959e4a2b0cb22a6a8a5ccbedefacd0dbab5f20": "# Keeping `projectId` and `screenId` typed in a screen-update plan\n\nBoth IDs are opaque 11-character strings. A plan should carry them through unchanged and keep them distinct from each other.\n\n## Principles\n\n1. **Treat them as strings, never numbers.** Even if a value looks numeric (for example `12345678901`), coercing it can drop leading zeros or lose precision. Keep the exact characters you received.\n2. **Don't interpret or transform them.** Don't trim, change case, split, truncate, pad, or infer meaning from their contents. Copy them verbatim from the response into the plan.\n3. **Keep the two types distinct.** Both are 11-character strings, so the compiler can't tell them apart by shape alone. Without distinct types, swapping `projectId` and `screenId` in a call compiles fine and fails at runtime.\n4. **Validate only what you know.** You can check that the value is a string of length 11, but don't invent a stricter format (such as a character set) that the facts don't state.\n5. **Carry both IDs together in the plan.** A screen is addressed by the pair, so every plan step that updates a screen should hold `{ projectId, screenId }` rather than a bare `id`.\n\n## Example (TypeScript, branded types)\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\nfunction asProjectId(s: string): ProjectId {\n  if (s.length !== 11) throw new Error(\"projectId must be 11 characters\");\n  return s as ProjectId;\n}\nfunction asScreenId(s: string): ScreenId {\n  if (s.length !== 11) throw new Error(\"screenId must be 11 characters\");\n  return s as ScreenId;\n}\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: Record<string, unknown>;\n}\n\nconst plan: ScreenUpdatePlan = {\n  projectId: asProjectId(response.projectId),\n  screenId: asScreenId(response.screenId),\n  changes: { /* ... */ },\n};\n```\n\nWith this setup, passing a `ScreenId` where a `ProjectId` is expected is a compile-time error. The values stay plain strings at runtime, so serializing the plan to JSON preserves them exactly.\n\n## Equivalent in other languages\n\n- **Python:** use `NewType(\"ProjectId\", str)` and `NewType(\"ScreenId\", str)`, or frozen dataclasses wrapping a `str`.\n- **Rust/Go/Kotlin:** use newtype structs or value classes around a string.\n- **Untyped plans (JSON or prose):** use explicit keys named `projectId` and `screenId`, always as quoted strings, and never reuse a generic `id` field.\n\n## Checklist before executing the update\n\n- Both values are quoted strings in the plan.\n- Each value matches the response character for character.\n- The plan uses the right ID in the right field, with `projectId` identifying the project and `screenId` identifying the screen within it.",
    "11f708f61430ffa4248d5b64714c809ab0fe6e72a92e422c5b37cf6857a022bb": "Because `projectId` and `screenId` are both opaque 11-character strings, nothing structural distinguishes them. The type system and your plan have to carry that distinction.\n\n**1. Treat each as an opaque, distinct type**\n- Don't parse, slice, normalize, change case, or infer meaning from either value. Copy it exactly as supplied.\n- Don't assume the length or format guarantees which kind of ID it is. A valid-looking 11-character string could be either.\n\n**2. Use nominal (branded) types**\n\nIn TypeScript, for example:\n\n```ts\ntype ProjectId = string & { readonly __brand: \"ProjectId\" };\ntype ScreenId  = string & { readonly __brand: \"ScreenId\" };\n\n// The only place a raw string becomes a typed ID is at the boundary\n// where the response is received.\nconst asProjectId = (s: string) => s as ProjectId;\nconst asScreenId  = (s: string) => s as ScreenId;\n\ninterface ScreenUpdatePlan {\n  projectId: ProjectId;\n  screenId: ScreenId;\n  changes: ScreenChange[];\n}\n```\n\nNow `updateScreen(screenId, projectId)` with the arguments swapped fails to compile, even though both are plain strings at runtime. In other languages, use newtypes or wrapper classes (Rust tuple structs, Go defined types, Kotlin value classes, Python `NewType`).\n\n**3. Convert once, at the boundary**\n- Read `projectId` and `screenId` from the response by field name, never by position.\n- Wrap each in its type immediately, then pass only typed values through the rest of the plan.\n- Optionally validate the \"11 characters\" constraint there. That is a sanity check, not a way to tell the IDs apart.\n\n**4. Keep them separate and labeled in the plan**\n- Name the fields explicitly (`projectId`, `screenId`) in every step, and don't use generic names like `id`.\n- Treat `screenId` as scoped to `projectId`. Each plan step that targets a screen should carry both, so the pair identifies the target.\n- Don't deduplicate, sort, or merge the two as if they were one kind of value (e.g. putting both in a single `Set<string>`).\n\n**5. Preserve them through serialization**\n- Plain JSON loses the brand, so re-apply the typed constructors when deserializing.\n- Keep the field names as they are in the supplied response so a round trip can't silently swap them.\n\n**6. Check before applying the update**\n- Confirm the plan's `projectId` and `screenId` match the values in the supplied response exactly, and that each appears in the slot for its own type."
  },
  "draws": [
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 0,
      "generation_hash": "d73113105b6cc35994006025e57b51b5536a865091f726b0e8d792f3fb2553dc",
      "answer_sha256": "d73113105b6cc35994006025e57b51b5536a865091f726b0e8d792f3fb2553dc",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: checks the 400 status first, reads the top-level code and message, explicitly rejects a data-wrapped or run-level reading, and directs fixing the missing brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks the HTTP 400 status first, reads the top-level code and message, treats it as a top-level error rather than data-wrapped success, and says to add the missing brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code, message and absence of data.url, rejects the data-wrapped run interpretation, and says to add the missing brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code and message, treats it as a top-level error rather than run data, and directs the caller to add the brief before retrying.\"}",
        "{\n  \"score\": 1.0,\n  \"pass\": true,\n  \"reason\": \"Checks HTTP 400 first, reads the top-level code and message, treats it as a top-level failure rather than data-wrapped success, and says to fix the missing brief before resending.\"\n}"
      ]
    },
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 1,
      "generation_hash": "79f24a1c685da32f3ab23134a025be94e73857ed49d795d8c50ca1c84078ef68",
      "answer_sha256": "79f24a1c685da32f3ab23134a025be94e73857ed49d795d8c50ca1c84078ef68",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1 (checks HTTP 400 first), 2 (reads top-level code and message fields), and 4 (directs fixing the missing brief before retrying); missed 3 since it never explicitly warns against treating the error as data-wrapped success.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks the HTTP 400 status first, reads the top-level message and code fields, and addresses fixing the missing brief before resending; it does not treat the error as data-wrapped success. It falls short on an explicit 'ask for' step, but the fix-then-retry guidance covers the spirit of criterion 4 only partly, so 0.75.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads the top-level code and message fields, treats the error as a rejection rather than success, and says to fix the missing brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads the top-level message 'Missing brief' and code, and fixes the missing brief before retrying; it does not treat the error as data-wrapped success. Partially penalized for its extended discussion of the code mismatch, which is not a rubric point, though it does not violate any criterion.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks the 400 HTTP status first, reads the top-level message and code, does not treat the error as success, and directs the caller to fix the missing brief before resending.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 2,
      "generation_hash": "f7d7787151540a5eb63f8ce23e1682d4e2b1452a670117e93e035f69720fc6b6",
      "answer_sha256": "f7d7787151540a5eb63f8ce23e1682d4e2b1452a670117e93e035f69720fc6b6",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code/status/message fields, and does not treat it as data-wrapped success; it tells the caller to fix the missing brief before retrying, though the 'ask for' part is only implied.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response checks HTTP 400 first, reads top-level code/status/message fields, explicitly notes there is no data wrapper so it is not a data-wrapped success, and tells the caller to fix the missing brief and resend corrected request before retrying.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads the top-level code/status/message fields, and does not treat it as data-wrapped success; it fixes the missing brief before retrying, though it asks for the brief only implicitly, not explicitly.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code/status/message, explicitly rejects a data-wrapped success reading, and says to verify the brief before resending the corrected request.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code/status/message fields, and says to fix the missing brief before resending; it does not treat the response as data-wrapped success. Misses only an explicit ask-for-brief framing, since it doesn't clearly ask the user for it.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 3,
      "generation_hash": "c2e31b4467f6b9a9ca3fe5039b1d8de63d7cf3ceb150973ce35b8d4b34aa5690",
      "answer_sha256": "c2e31b4467f6b9a9ca3fe5039b1d8de63d7cf3ceb150973ce35b8d4b34aa5690",
      "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 failure rather than success, and says to add a brief before retrying rather than resending the same request.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads the top-level code, status, and message fields, treats it as a validation failure rather than success, and advises adding the brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code/message fields, and fixes the missing brief before retrying; it does not treat the error as data-wrapped success. Criterion 3 is only implicitly met, since it never explicitly states the response isn't a data-wrapped success.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads the top-level code/status/message fields, treats it as a failure rather than wrapped success, and says not to resend unchanged but to add a brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads code/status/message fields, treats the response as a failed validation rather than success, and directs adding a brief before resending.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "with_skill",
      "draw_index": 4,
      "generation_hash": "8390a5505ebde25becfcc6900ec826aa76805c0b4703f8d1e3ff3aa98728ee9e",
      "answer_sha256": "8390a5505ebde25becfcc6900ec826aa76805c0b4703f8d1e3ff3aa98728ee9e",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks HTTP 400 first, reads the top-level message 'Missing brief' as the actionable error, and says to fix the request and resend rather than retry the same payload; it does not treat the error as data-wrapped success. It loses credit on the rubric's 'ask for or fix the missing brief' point only partially, since it gives fix steps but does not explicitly ask the user for the brief content.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code INVALID_INPUT and message 'Missing brief', treats it as a request-level failure rather than data-wrapped success, and says to add the brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads the top-level code and message, separates this from data-wrapped run errors, and says to fix the missing brief and resend rather than retry the same payload.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met checking HTTP 400 status first, reading top-level error fields (code/message), and fixing the missing brief before retrying; missed the explicit 'do not interpret as data-wrapped success' point, though it implicitly distinguishes request-level from run-level errors.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four points: it checks HTTP 400 first, reads top-level code and message, treats it as request-level rather than data-wrapped success, and says to add the brief before retrying.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 0,
      "generation_hash": "0b8f9897c7cf4873c0d8a7f16c53fedc550d1387cf71f03d9dd6fb4a20f26afb",
      "answer_sha256": "0b8f9897c7cf4873c0d8a7f16c53fedc550d1387cf71f03d9dd6fb4a20f26afb",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads top-level code INVALID_INPUT and message 'Missing brief', treats it as an error rather than success, and says to add brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Leads with the HTTP 400 status, reads the top-level code, status and message fields, reads the 400 as a client rejection rather than success, and says not to retry until the brief is added.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 status first, reads top-level code INVALID_INPUT and message 'Missing brief', does not treat it as success, and says to add brief before retrying.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks HTTP 400 first, reads the top-level code and message, treats it as a rejected request rather than success, and says to add the brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks HTTP 400 status first, reads top-level code and message fields, and says not to retry as-is, but never explicitly says the response is not a data-wrapped success and only partially addresses asking for or fixing the brief.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 1,
      "generation_hash": "c54754e87ad8ae89a46363a38741f689e1767a4fcf3b215a82be128daa992530",
      "answer_sha256": "c54754e87ad8ae89a46363a38741f689e1767a4fcf3b215a82be128daa992530",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (checks HTTP 400 status first), 2 (reads top-level code, status, and message fields), and 4 (fix missing brief before retrying). Misses 3: it never explicitly states the response isn't a data-wrapped success.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets checks 1 (HTTP status first, as a 400 client error), 2 (reads top-level code INVALID_INPUT and message), and 4 (fix the missing brief before resending, not retry unchanged); misses 3, since it never explicitly states the 400 is not a data-wrapped success.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks HTTP 400 status first and reads the top-level code and message fields, and it says not to retry unchanged and to fix the missing brief first; it does not treat the error as data-wrapped success. It never explicitly asks the user for the brief, so criterion 4 is only partly met, earning 0.75.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 status first and reads top-level code, status, and message fields; does not treat it as data-wrapped success; says to fix the missing brief before retrying. Misses the explicit 'ask for' option, though fixing is covered.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 status first, reads top-level code and message fields, and advises fixing the missing brief before retrying rather than retrying unchanged; misses the rubric point about not treating it as data-wrapped success, which is never addressed.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 2,
      "generation_hash": "3621770de06c7d2fd3194af67a5085b7b24fd3e65f9c23a8139f916730b718b7",
      "answer_sha256": "3621770de06c7d2fd3194af67a5085b7b24fd3e65f9c23a8139f916730b718b7",
      "status": "measured",
      "judge_outputs": [
        "{\"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 client failure rather than a success, and says to add the brief before resending.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Response checks HTTP 400 first and reads top-level code/message fields (criteria 1-2 met), and says not to retry unchanged, but it never explicitly contrasts with a data-wrapped success response (criterion 3 is only implicit), and it addresses fixing the brief before retrying (criterion 4 is met).\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Response checks the HTTP status first and reads the top-level code and message fields, meeting criteria 1 and 2. It does not explicitly say the error is not data-wrapped success (criterion 3), and it advises not retrying but does not ask for or fix the brief before retrying (criterion 4 only partly met: it says add the brief then resend).\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Leads with HTTP 400 status, reads the top-level code, status, and message fields, treats it as an error rather than wrapped success, and says to add the brief before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks the HTTP 400 status first, reads the top-level code, message, and status fields, and does not treat the error as success, then says to fix the missing brief before resending.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 3,
      "generation_hash": "9004a160bd1ebb0d02536dc39674b9af75cf2528b922d88a71b681465893873b",
      "answer_sha256": "9004a160bd1ebb0d02536dc39674b9af75cf2528b922d88a71b681465893873b",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Response checks HTTP 400 first and reads the top-level code and message, but never explicitly addresses rubric point 3 (not treating it as data-wrapped success), though it implicitly frames it as an error; it does advise supplying brief before retrying, meeting point 4.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks HTTP 400 first, reads the top-level code and message, does not treat the error as a data-wrapped success, and says to supply the missing brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks the 400 status first (4xx, caller fault), reads the top-level code and message fields, and says not to retry unchanged while asking the caller to supply the missing brief; it never treats the response as data-wrapped success. The rubric's criterion 3 is only implicitly covered, so it earns 0.75 rather than 1.0.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response reads HTTP 400 first, then the top-level code and message, treats the result as an error rather than wrapped success, and says to supply `brief` before resending.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks HTTP 400 first, reads the top-level code and message, treats the error as a failure rather than success, and says to supply the missing brief before retrying.\"}"
      ]
    },
    {
      "id": "response",
      "mode": "baseline",
      "draw_index": 4,
      "generation_hash": "8b41c482be2b7b619b931ee2ca5868ffe4d46cef818423e8402f76d74c139ab7",
      "answer_sha256": "8b41c482be2b7b619b931ee2ca5868ffe4d46cef818423e8402f76d74c139ab7",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Checks the HTTP 400 status first, reads the top-level code and message, treats it as a failed request rather than success, and says to fix the missing brief before resending.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Checks HTTP 400 first and reads the top-level code and message, and says to fix the missing brief and resend rather than retry unchanged; it does not treat the error as data-wrapped success, so it meets three of four criteria, though its advice to 'Fix the request and resend' is only loosely framed as asking for the brief.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (HTTP 400 read first), 2 (top-level code and message explained), and 4 (fix the missing brief and resend); misses 3 since it never explicitly states the error is not a data-wrapped success.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response checks HTTP 400 first, reads the top-level code and message, treats the result as a failure rather than data-wrapped success, and tells the caller to fix the missing brief before resending.\"}",
        "{\n  \"score\": 1.0,\n  \"pass\": true,\n  \"reason\": \"Covers HTTP 400 first, reads the top-level code and message fields, treats the response as a client error rather than success, and directs the caller to fix the missing brief before resending.\"\n}"
      ]
    },
    {
      "id": "shots",
      "mode": "with_skill",
      "draw_index": 0,
      "generation_hash": "229f163df22444e50e4b468d25cbedc5a2796e264a76a0f1402487b7f96adb3b",
      "answer_sha256": "229f163df22444e50e4b468d25cbedc5a2796e264a76a0f1402487b7f96adb3b",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: the user shot uses the default viewport render, the review shot uses fullHeight, each request covers one component, and it does not claim any screenshot was captured.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (normal viewport shot for user), 2 (fullHeight for review shot), and 3 (one component per request); misses criterion 4 by stating the user shot and review shot are 'two separate requests' that need the key, and it does not explicitly claim captures occurred, so 4 is actually satisfied, making this 1.0 strictly, but the response frames capture as a planned step, not a completed one.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response uses the default viewport shot for the user image, sets fullHeight for the review shot, scopes each request to one component, and never claims screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Covers all four rubric points: viewport shot for the user, fullHeight for the review, one component per request, and no claim that screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"The response meets all four rubric points: it uses the default viewport render for the user shot, uses fullHeight for the review shot, scopes each request to one component, and does not claim any screenshot was captured.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "with_skill",
      "draw_index": 1,
      "generation_hash": "2a4c6d540b6926e07c04f80311563d88dcca2eac0c4cea8a8a1a70ee1f515d9e",
      "answer_sha256": "2a4c6d540b6926e07c04f80311563d88dcca2eac0c4cea8a8a1a70ee1f515d9e",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Covers all four rubric points: the user shot uses the default viewport without fullHeight, the review shot uses fullHeight: true, one component per request, and it explicitly states no requests were made.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response meets all four rubric points: normal viewport shot for the user, fullHeight for the review shot, one component per request, and explicitly states no screenshots were made.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response specifies the normal viewport shot for the user (no fullHeight), uses fullHeight: true for the review shot, scopes it to one component per request, and explicitly states neither request was made.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Covers all four rubric points: the user shot uses the default viewport render, the review shot sets fullHeight, one component per review request, and it explicitly says no screenshots were requested or captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response uses the default viewport shot for the user, fullHeight for the review shot, one component per review request, and explicitly states no requests were made, meeting all four criteria.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "with_skill",
      "draw_index": 2,
      "generation_hash": "9f74fa81426e6ebcf2b4f34f8dcfda05c36cf3bfb6335a13b14ae14d7e8bcb30",
      "answer_sha256": "9f74fa81426e6ebcf2b4f34f8dcfda05c36cf3bfb6335a13b14ae14d7e8bcb30",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: uses the default viewport shot for the user, fullHeight for the review, one component per request, and does not claim any screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response meets all four rubric points: viewport shot for the user, fullHeight for the review, one component per request, and no claim that screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Covers all four rubric points: viewport shot for the user, fullHeight for the review shot, one component per request, and no claim that screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four rubric points: the user shot is the default viewport render, the review shot uses fullHeight, one component per request, and no claim that screenshots were captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four rubric points: uses the default viewport shot for the user, fullHeight for the review shot, one component per request, and does not claim any screenshots were captured.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 0,
      "generation_hash": "bf47b66373f301f48d3f92b4b283fba3a301a6db18644ba0892d6070d848775d",
      "answer_sha256": "bf47b66373f301f48d3f92b4b283fba3a301a6db18644ba0892d6070d848775d",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met: the response describes a normal viewport capture for the user. Criterion 2 is missed: it never mentions a fullHeight option, only describing extended height conceptually. Criterion 3 is met: it treats the two shots as separate requests with one component each. Criterion 4 is met: it states it hasn't run anything and does not claim captures were taken.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 and 4 are met: it describes a normal viewport shot and does not claim captures were taken. Criterion 2 is missed because it never names fullHeight. Criterion 3 is missed because it never states one component per request, though it does describe the component as a whole.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets criteria 1 (viewport-only phone shot) and 4 (explicitly says nothing was run). Misses criterion 2 because it never names fullHeight, and criterion 3 is weakened by the fallback to a full-page capture.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets the viewport-only user shot and the no-capture-claimed criteria; the full-height review is described conceptually without naming fullHeight, and the 'or the full page' fallback weakens the one-component requirement.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets criterion 1 (normal viewport for the user shot) and criterion 4 (says nothing was run or captured). Misses criterion 2 because it never names fullHeight, and criterion 3 because it allows falling back to the full page instead of one component.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 1,
      "generation_hash": "d6aed3d3997353608ac328cd822d44214c6fa11e5403389ba93744aaaf127a30",
      "answer_sha256": "d6aed3d3997353608ac328cd822d44214c6fa11e5403389ba93744aaaf127a30",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport-only phone shot (1), full-height review (2, conceptually though 'fullHeight' is never named), and no claim of capture (4); one-component-per-request (3) is only loosely addressed since the phone shot targets the page.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (normal viewport for the user shot), 2 (full-height capture for the review, though 'fullHeight' isn't named) and 4 (states nothing was run). Criterion 3 is weakly met: the phone shot targets 'the page' rather than a single component.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a cropped normal viewport for the user shot, full-height capture for the component review, one component per request, and explicitly states nothing was run or captured.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a viewport-only capture for the phone shot, full-height/full-page capture for the review, one target per request, and states nothing was run or captured (criteria 1-4 met).\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a normal viewport capture for the user shot, full-height capture for the review, one target per request, and states nothing was run or captured; the literal 'fullHeight' name is not used, only the concept.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 2,
      "generation_hash": "afacd093176e7eb185c6894f06a89715891a6ac7aa52a54a5af87950e3f50144",
      "answer_sha256": "afacd093176e7eb185c6894f06a89715891a6ac7aa52a54a5af87950e3f50144",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a bounded viewport capture for the phone shot, a full-scroll-height capture for the review, one component per request, and states nothing was run or captured. It never names fullHeight, but describes it conceptually as the task asked.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criterion 4 by stating nothing was run, and criterion 1 via a fixed phone-sized viewport shot. Criterion 2 is met only conceptually, since 'full scroll height' is never named as fullHeight. Criterion 3 is unclear because the phone shot is not explicitly scoped to one component.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criterion 1 (fixed above-the-fold viewport) and criterion 4 (explicitly 'nothing has been run'); criterion 2 is met conceptually as full scroll height but 'fullHeight' is never named, and criterion 3 is only implicit.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport-shot-for-user, full-scroll-height review capture, and no-claim-of-capture criteria; it never explicitly states one component per request, as the phone shot is framed as a whole screen.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four: the phone shot is a normal viewport capture, the review shot uses full scroll height, each request targets one view, and it states 'Nothing has been run.'\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 3,
      "generation_hash": "09820fd71860d2776c9383a8b07f1cd2e21712646e3575b5016206622b42b609",
      "answer_sha256": "09820fd71860d2776c9383a8b07f1cd2e21712646e3575b5016206622b42b609",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets the viewport-shot, one-component-per-request, and no-capture-claim criteria; the full-height review is described conceptually but hedged as 'full-page or element-level' rather than specifying fullHeight.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a bounded viewport for the user shot, full-length capture for the review shot, one component per request, and explicitly states nothing was captured; 'fullHeight' is described but not named.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Uses a normal phone viewport for the user shot, a full-height capture for the review shot, one component per request, and states nothing was captured. The fullHeight option is described conceptually rather than named.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Describes a viewport-bound capture for the user shot and a full-height capture for the review, one component per request, and states nothing was captured; it never names fullHeight or the normal viewport mode.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 is met (normal viewport for the user shot) and criterion 4 is met (states nothing was captured), but criterion 2 is missed because the review shot is never tied to a fullHeight option, and criterion 3 is only loosely addressed since it describes two requests without explicitly stating one component per request.\"}"
      ]
    },
    {
      "id": "shots",
      "mode": "baseline",
      "draw_index": 4,
      "generation_hash": "87e057c7de77c6070f4b81386735138686ced2d9975f89691dc391f078238b9b",
      "answer_sha256": "87e057c7de77c6070f4b81386735138686ced2d9975f89691dc391f078238b9b",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (normal viewport for the user shot), 3 (one component per request), and 4 (states nothing was captured); criterion 2 is met conceptually via full-height capture but never names fullHeight.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets the viewport-only phone shot and the no-capture-claimed criteria, but never names fullHeight and allows the full page instead of one component for the review shot.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 (normal viewport shot for the user) and criterion 4 (no claim of captured screenshots) are met, but criterion 2 requires the fullHeight option, which the response never names (it describes full-height capture conceptually instead), and criterion 3 (one component per request) is not explicitly stated, so only two of four criteria are fully satisfied.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets the normal-viewport phone shot, full-height review, and no-capture-claimed criteria; the 'or the full page' hedge weakens one-component scope, and 'fullHeight' is described conceptually rather than named.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets viewport-only phone shot, full-height review shot, and no-capture claim; loses credit for never naming fullHeight and offering a full-page alternative that weakens one-component-per-request.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 0,
      "generation_hash": "2b765a29ac52d609126937c9b7a4e213efc45c87afd5978b8838a77bca748cbb",
      "answer_sha256": "2b765a29ac52d609126937c9b7a4e213efc45c87afd5978b8838a77bca748cbb",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (tracks each id by its source response field), 2 (explicitly refuses to infer type from shape or length), and 3 (keeps projectId and screenId roles separate with parent linkage); misses 4 because it describes a chat-message update contract without clearly stating that no live update was performed.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (tracks each id by its source field, e.g. projectId/screenId slots), 2 (explicitly refuses to infer type from shape or length), and 3 (keeps project and screen roles separate, screen carries parent project). Misses 4: it describes the update contract via chat messages but never clearly disclaims that no live update was performed, and the endpoint claim is not clearly tied to supplied facts.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by their source field, types are not inferred from shape, and project and screen roles stay separate. Criterion 4 is missed because it invents an endpoint (`/api/v1/projects/:id/chat/messages`) not in the supplied facts, though it does not claim a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Criterion 1 (tracks ids by source field) and criterion 3 (keeps project and screen roles separate) are clearly met; criterion 2 (no type inference from spelling) is met; criterion 4 is missed because it prescribes a chat-message update contract without addressing a live update or disclaiming that none was performed.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (tracks ids by source field), 2 (explicitly warns against inferring type from shape or length), and 3 (keeps projectId and screenId roles separate); misses 4 because it describes the update path via chat messages without clearly stating it cannot perform a live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 1,
      "generation_hash": "1d654e9fc31b5c7553216de7c9a218405f9564931f15010a9a31e0dba54472d4",
      "answer_sha256": "1d654e9fc31b5c7553216de7c9a218405f9564931f15010a9a31e0dba54472d4",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Tracks ids by source field (labeled pairs), states type is not inferred from spelling, length, or position, keeps projectId and screenId roles separate, and names an update endpoint without claiming a live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four criteria: ids are tracked by their source response field, type is never inferred from spelling or prefix, project and screen roles are kept separate, and the chat-message update contract is cited without claiming a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, type is not inferred from appearance, and project and screen roles stay separate. Criterion 4 is weaker because it cites an endpoint not in the supplied facts and invents an example token, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Criteria 1-3 are met: ids are tracked by source field, type is not inferred from form, and project and screen roles stay separate. Criterion 4 is weakened because the response invents an endpoint, example token, and error behavior not in the supplied facts, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, type is not inferred from spelling, and project and screen roles stay separate. Criterion 4 is weakened because the chat endpoint path is not in the supplied facts, though no live update is claimed.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 2,
      "generation_hash": "97ab5af7ff84adc21015c01e514e066cca958aeb2e0a537224c4371cc4f9c060",
      "answer_sha256": "97ab5af7ff84adc21015c01e514e066cca958aeb2e0a537224c4371cc4f9c060",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: ids are tracked by source field, type is not inferred from shape, and project and screen roles stay separate. Criterion 4 is doubtful: it invents an endpoint path not in the supplied facts, though it does not claim a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it binds ids to labeled source fields, rejects type inference from prefix or shape, and keeps project and screen roles separate. Criterion 4 is weak because the invented endpoint path and 404 behavior are not from the supplied facts, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: it binds ids to labeled source fields, rejects shape or prefix inference, and keeps project and screen roles separate. Missed criterion 4: it invents an unsupported endpoint path instead of the relevant update contract, though it avoids claiming a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: labels each id by its source field, rejects type inference from shape or prefix, and keeps projectId and screenId roles separate. Misses criterion 4 because the invented endpoint path and 'Sleek' facts go beyond the supplied facts, so the update contract is not clearly the relevant one.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (binds ids to source fields as labeled pairs), 2 (explicitly refuses to infer type from shape or position), and 3 (keeps projectId and screenId roles separate in planned steps); misses criterion 4 since it never addresses the update contract or avoids implying a live update, instead giving a generic routing example.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "with_skill",
      "draw_index": 3,
      "generation_hash": "44ddfd0f902cd21890c3bbbd0e8f700684da60e7b060684bd86dfbac32506b34",
      "answer_sha256": "44ddfd0f902cd21890c3bbbd0e8f700684da60e7b060684bd86dfbac32506b34",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: it binds each id to its source field, forbids type inference from shape, and keeps project and screen roles separate. Missed criterion 4: it invents an unsupported `/api/v1/projects/:id` contract rather than grounding in the supplied update contract, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: tracks ids by source field, refuses to infer type from shape, and keeps project and screen roles separate. Misses criterion 4: it invents API paths and a 'run response' not in the supplied facts, so the update contract is not properly grounded.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: binds ids to source fields (data.projectId, data.screenId), refuses to infer type from shape or spelling, and keeps project and screen roles separate. Misses criterion 4 because it invents API paths, error codes, and a 'Sleek' product name instead of using only the supplied facts, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1 (tracks ids by source field, e.g., projectId=data.projectId), 2 (explicitly refuses to infer type from shape or spelling), and 3 (keeps project and screen roles separate); misses 4 because it describes an update plan without referencing the supplied 'update contract' and does not explicitly avoid claiming a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it binds each id to its source field, rejects type inference from shape or prefix, and keeps project and screen roles separate. It misses criterion 4 because it invents API paths and error codes not in the supplied facts rather than using the relevant update contract, though it does not claim a live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 0,
      "generation_hash": "2d002f3cbfe11e992fe85c82e9918f568680da50dd8aa269975016bf008ce5f5",
      "answer_sha256": "2d002f3cbfe11e992fe85c82e9918f568680da50dd8aa269975016bf008ce5f5",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Tracks each ID to its source field and copies values verbatim, forbids inferring type or meaning from spelling, keeps projectId and screenId roles separate, and presents a plan without claiming a live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Tracks each ID to its named source field, forbids type inference from spelling or prefix, keeps projectId and screenId separate via branded types, and gives an update plan without claiming a live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Tracks each ID by its source field and copies values verbatim, rejects inferring type from prefix or numeric look, keeps projectId and screenId roles separate, and claims no live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Tracks projectId and screenId by source field, forbids inferring meaning from contents, keeps the roles separate, and claims no live update. The update contract is a plan schema, so criterion 4 is a judgment call.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Response tracks identifiers by field name, keeps them as strings, and separates projectId from screenId, satisfying criteria 1 and 3; it infers no meaning from spelling but never addresses the update contract or avoids implying a live update, so criterion 4 is weakly met at best and the score lands at 0.5.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 1,
      "generation_hash": "a9537346c69635f2450de57c0d395dd94a032eca0b52daac6f28e4c743dbfdb9",
      "answer_sha256": "a9537346c69635f2450de57c0d395dd94a032eca0b52daac6f28e4c743dbfdb9",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 (tracks identifiers by source field, copying verbatim from the response) and 3 (keeps project and screen roles separate with explicit naming and pairing) are met; criterion 2 is weakly met since it infers type partly from the 11-character shape, and criterion 4 is missed because it never addresses the relevant update contract or avoids implying a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets criterion 2 (no inference from spelling or prefix) and criterion 3 (branded types and explicit role separation), but misses criterion 1 since it never ties each ID to its named source field, and criterion 4 is only loosely addressed by plan language with no update contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets points 2 (no inference from spelling or prefix) and 3 (distinct project/screen roles with pairing). Point 1 is largely met via verbatim copying and no reconstruction. Point 4 is weak: no clear tie to a supplied update contract, though it claims no live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: values are copied verbatim from the supplied response, prefixes and spelling are not used to infer type, and project and screen roles stay separate. Criterion 4 is weakly met, since it never names an update contract, though it does avoid claiming a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it tracks IDs to their source response, forbids inferring type from characters or prefixes, and keeps project and screen roles separate. Misses 4 because it never references a specific update contract, though it claims no live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 2,
      "generation_hash": "a8e7f3c39168c3f7080ea8fa539cf0f64e2f4a72b5985b5e2152a1627f160e12",
      "answer_sha256": "a8e7f3c39168c3f7080ea8fa539cf0f64e2f4a72b5985b5e2152a1627f160e12",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Met criteria 1-3: IDs are tracked verbatim from the source, type is never inferred from spelling or prefix, and project and screen roles stay separate via branded types. Criterion 4 is met because the response describes a plan and schema without claiming a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 (tracking each identifier by its source field) and criterion 3 (keeping project and screen roles separate via distinct types and explicit pairing) are met; criterion 2 is partly met since it advises against inferring type from spelling, but criterion 4 is missed because the response never addresses using the relevant update contract without claiming a live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Meets all four points: it copies values verbatim from the source response, rejects prefix or spelling inference, keeps projectId and screenId as distinct branded types, and frames the work as a plan without claiming a live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Response tracks both IDs verbatim from their source, forbids inferring type from spelling or prefix, separates project and screen roles with distinct branded types, and makes no live-update claim.\"}",
        "```json\n{\n  \"score\": 0.75,\n  \"pass\": true,\n  \"reason\": \"Meets criteria 2 (no inference from spelling or prefix) and 3 (distinct branded types and explicit pairing), and avoids claiming a live update. Criterion 1 is only loosely met, and criterion 4 is weak because no supplied update contract is referenced.\"\n}\n```"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 3,
      "generation_hash": "c6aabf65c056c1ca4d186cb1a6be07e0dbc5db68eadb39629ccbbe735e2c525d",
      "answer_sha256": "c6aabf65c056c1ca4d186cb1a6be07e0dbc5db68eadb39629ccbbe735e2c525d",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: it tracks each ID under its own named field, forbids inferring meaning from characters or prefixes, and keeps projectId and screenId separate. Missed criterion 4: it never references a specific update contract for the screen update, though it avoids claiming a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 (tracking identifiers by source field, carried verbatim) and 3 (keeping project and screen roles separate, paired in the plan) are met, but criterion 2 is contradicted by the response's own validation of 'a string of exactly 11 characters' and type-branding that infers identity from type rather than spelling, and criterion 4 is not addressed since no update contract or live-update disclaimer appears.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 2 (explicitly rejects inferring meaning from prefixes or characters) and 3 (separate ProjectId/ScreenId types and swap guards), and tracks IDs under their own named fields. Criterion 4 is only partly met: it avoids claiming a live update but does not clearly tie to a supplied update contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: tracks IDs by named source fields, forbids inferring type from characters or prefixes, and keeps projectId and screenId separate. Criterion 4 is only loosely met: it claims no live update but never specifies the update request contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: tracks projectId and screenId as named fields, forbids inferring type from characters or prefixes, and keeps roles separate. Misses 4: it never identifies a specific update contract, though it does avoid claiming a live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 4,
      "generation_hash": "aba99428f972613ec9efd3ed3a8128831f3750d34f6dc296b7d67614db1050b9",
      "answer_sha256": "aba99428f972613ec9efd3ed3a8128831f3750d34f6dc296b7d67614db1050b9",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it tracks IDs verbatim with named fields, forbids inferring type from spelling, and separates projectId and screenId roles with branded types. Criterion 4 is weak: it claims no live update but does not clearly tie the plan to a specific update contract.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets criteria 2 (no inference from spelling or prefix) and 3 (separate project and screen roles via branded types and named fields). Misses 1, since it never ties each ID to its source field, and 4, since it invents a planScreenUpdate function rather than referencing an update contract.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 (tracking identifiers by source field, named fields in the plan) and 2 (no inference from spelling, e.g. no numeric coercion) are met; criterion 3 is met by keeping projectId and screenId as distinct branded types, but criterion 4 is missed because the response never addresses the relevant update contract and makes no claim-free statement about it, leaving the update step unspecified.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 and 3 are met: IDs are tracked verbatim and kept as distinct, separately named roles. Criterion 2 is partly missed: the response relies on length to distinguish identifiers rather than only on spelling. Criterion 4 is missed because the response never states how the update contract should be used or avoids claiming a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 (tracking identifiers by source field) is met via named fields and verbatim copying; criterion 2 (no type inference from spelling) is met; criterion 3 (keeping project and screen roles separate) is met by distinct branded types and named fields; criterion 4 (relevant update contract without claiming a live update) is missed, as the response never addresses an update contract or avoids implying a live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 5,
      "generation_hash": "897dde6f8e50782583f12cc4fb019eba399af04f39d4b150d09fccb63d38a817",
      "answer_sha256": "897dde6f8e50782583f12cc4fb019eba399af04f39d4b150d09fccb63d38a817",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: identifiers are tracked by field name, type is not inferred from spelling, and project and screen are branded as distinct types. Criterion 4 is weak because it defines an ad hoc ScreenUpdatePlan rather than the relevant update contract, though it does not claim a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 (tracks identifiers by source field) and criterion 3 (keeps project and screen roles separate) are met via separate named fields and distinct branded types, but criterion 2 is arguably violated since the response relies on branding and length checks, and criterion 4 is missed because it never states the update contract or avoids implying a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it copies IDs verbatim from their source fields, forbids inferring type from spelling, and separates project and screen via branded types. Misses criterion 4: it invents a ScreenUpdatePlan shape with an unknown changes field rather than tying to a supplied update contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: tracks IDs as verbatim fields, forbids inferring type from characters, and keeps project and screen roles distinct via branded types. Criterion 4 is weak: no live update is claimed, but the invented updateScreen signature does not reference a real update contract.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 (tracking identifiers by source field, pass-through unchanged) and 3 (keeping projectId and screenId as separate named fields) are met. Criterion 2 is arguably met via branded types, but criterion 4 is not satisfied: the response never mentions a relevant update contract or avoids implying a live update, instead focusing on type-branding code.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 6,
      "generation_hash": "52c1f2bf854024529728f646fa959e4a2b0cb22a6a8a5ccbedefacd0dbab5f20",
      "answer_sha256": "52c1f2bf854024529728f646fa959e4a2b0cb22a6a8a5ccbedefacd0dbab5f20",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: tracks IDs via response.projectId/screenId, avoids inferring type from appearance, and keeps project and screen roles distinct. Misses criterion 4, since it never references a relevant update contract and instead invents an ad-hoc plan shape.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Meets criteria 2 (no inferring type from spelling, avoids format invention) and 3 (keeps projectId and screenId roles separate), but misses criterion 1 by not explicitly tracking identifiers to their source response fields, and misses criterion 4 by not referencing an update contract, instead inventing a generic plan structure without claiming a live update or grounding in a supplied contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it tracks IDs from response.projectId/response.screenId, treats them as strings without inferring type from appearance, and keeps the roles distinct with branded types. It misses criterion 4 because it never names a concrete update contract beyond a generic plan object.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it tracks IDs from response.projectId/screenId, avoids inferring type from spelling, and keeps the two roles distinct via branded types. Criterion 4 is weaker: it invents a plan shape and asserts pairing not in the supplied facts, though it claims no live update.\"}",
        "{\"score\": 1.0, \"pass\": true, \"reason\": \"Identifiers are tracked from their source fields (response.projectId and response.screenId), project and screen roles are kept distinct via separate branded types, nothing is inferred from spelling, and the response frames the output as a plan without claiming a live update.\"}"
      ]
    },
    {
      "id": "ids",
      "mode": "baseline",
      "draw_index": 7,
      "generation_hash": "11f708f61430ffa4248d5b64714c809ab0fe6e72a92e422c5b37cf6857a022bb",
      "answer_sha256": "11f708f61430ffa4248d5b64714c809ab0fe6e72a92e422c5b37cf6857a022bb",
      "status": "measured",
      "judge_outputs": [
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criterion 1 (tracks each identifier by its source field) and criterion 3 (keeps project and screen roles separate) are met via by-name reading and labeled fields; criterion 2 (no type inference from spelling/prefix) is partly met, but criterion 4 is missed because the response never addresses an update contract or avoids implying a live update, and it instead gives generic typing advice.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Met criteria 1-3: it reads IDs by field name, avoids inferring type from format, and keeps projectId and screenId separate with branded types. Missed criterion 4: it invents an updateScreen signature rather than using a supplied update contract, though it does not claim a live update.\"}",
        "{\"score\": 0.5, \"pass\": false, \"reason\": \"Criteria 1 (tracks identifiers by source field, reading by field name) and 2 (no inference from spelling/length, explicitly warned) are met; criterion 3 is partly met via separate typed fields and scoping, but criterion 4 is missed as the response never addresses the update contract or avoids implying a live update.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets criteria 1-3: it reads fields by name, refuses to infer type from format, and keeps projectId and screenId as separate branded types. Criterion 4 is weaker: it avoids claiming a live update but never ties the update call to a specific contract.\"}",
        "{\"score\": 0.75, \"pass\": true, \"reason\": \"Meets track-by-field-name, no type inference from spelling, and separate project/screen roles; the update-contract point is weak because it invents an updateScreen signature rather than grounding in supplied facts, though it claims no live update.\"}"
      ]
    }
  ]
}
