Skip to content

Prepare 3.1.0 for a paid web pilot - #93

Merged
gabe4coding merged 2 commits into
mainfrom
cursor/web-pilot-3-1-0-7357
Oct 19, 2026
Merged

gabe4coding merged 2 commits into
mainfrom
cursor/web-pilot-3-1-0-7357

Conversation

@gabe4coding

@gabe4coding gabe4coding commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Prepares @gabe4coding/plain 3.1.0 for a paid browser pilot. Do not merge until you want this published: the Publish workflow on main publishes the version in package.json and tags v3.1.0. This PR does not publish, tag, or create a GitHub release.

Follow-up on 0468b4c (after live review of cab4bb2): version 1 lock files are read again, and four smaller fixes landed. Not merged, not published, no tag.

Follow-up: version 1 locks and the review notes

  • Lock files. parseLock accepts version 1 and version 2. Locators did not change, so --mode no-judge replays them. A version 1 claim hash that does not match is judged again (a re-verify) instead of an inconclusive "page differs". The first read prints one line: plain: re-record to upgrade lock file: <path>. A passing judge or auto-healing run writes version 2. Version 0 is still ignored. Tests: src/core/lock.test.ts (read + the one-line notice) and src/browser/lock.test.ts (a version 1 file replays in no-judge with no Jev call; a hash of zeros is judged again, the locator still replays, and no-judge does not rewrite the file). Desktop and mobile use the same store and the same re-verify.
  • Long context. The uncut tree sent on the retry is capped at 400,000 characters (LONG_CONTEXT_MAX_CHARS, about 100k tokens, inside the 262k window). A settle re-check in the same check does not send long context again. Tests: the cap in src/core/automation.test.ts, and src/browser/expect-advice.test.ts (a ticking cut page sends long context once).
  • Node message. plain-computer and plain-mobile pass their own command name into refuseOldNode. A Node 20 process says plain-computer requires or plain-mobile requires, not plain requires. Covered by scripts/node-check.test.mjs.
  • Screenshot line. An expect with within does not send the viewport screenshot. The shot is the whole viewport, so it must not say a region claim holds. The split-text hint can still appear. Test: src/browser/expect-advice.test.ts.
  • Split-text hint. splitTextHint returns false for a spatial claim (sureSpatial). 2 items left stays a count claim, because that left is not a relation. Test: src/browser/split-text.test.ts.

1. TypeSafe SDK 0.7 and jev-1.14.0

  • @typesafe-ai/sdk is ^0.7.0 (lockfile 0.7.0). The TypeSafe model is jev-1.14.0 in MODEL_BY_PROVIDER. The gateway model stays typesafe-ai/jev.
  • SDK 0.7 types usage.input_tokens and usage.output_tokens as number | null. tokenCount in src/jev/ask.ts treats null as zero. Covered by src/jev/ask.test.ts.
  • Claim thresholds are unchanged (pass ≥ 0.9, fail ≤ 0.1, pick ≥ 0.5). The docs say those thresholds and the phrasing advice apply to jev-1.14.0.

2. Failure screenshots, on by default

A browser expect that does not pass, and that has no within, sends the viewport PNG plain already captures. The call uses image() with header TypeSafe-Danger-Zone-Images: true. Sides must be 280–1568 px and the file must be at most 16 MiB; anything outside that range is not sent (TypeSafe rejects it, and plain does not resize).

The step detail gets a screenshot: line (holds, contradicted, or not visible, with the probability). That line never changes pass or fail. A thrown image call, unreadable bytes, or no configured provider omits the line and leaves the status. The gateway path does not support images: ask() throws before sending, and the detail says screenshot: not sent (the Vercel AI Gateway path has no image support).

Evidence: src/jev/explain.test.ts and src/browser/expect-advice.test.ts (a pass does not call explain; a fail stays fail; a throw keeps the fail; a scoped within check does not call explain; a zero-token gateway line is printed and not counted).

3. Long context only after the 60k cut

When the capture set truncated and the uncut tree is longer than the text actually sent, judgeState asks once more with TypeSafe-Danger-Zone-Long-Context: true and uses that answer. The tree is cut at 400,000 characters. A failed retry keeps the first verdict. If the page changes while that check settles, the later look uses the cut tree and does not send long context again. The gateway path throws before sending, and that error is caught so the cut verdict stays.

The uncut tree is fullAria on the capture, not on the public snapshot. New lock hashes include it, so new files are version 2. Version 1 files are still read (see the follow-up above). There are no committed lock files in this repo.

Evidence: src/core/automation.test.ts and the long-page cases in src/browser/expect-advice.test.ts.

Live e2e on cab4bb2 (run 39158122212): the long /consent page judged with (long context) and the expected passes stayed passes (p=0.97). The expected failure on that page stayed a failure (p=0.01) with the screenshot line. No spec passed only on a retry. This follow-up has not been re-run live yet (see Validation).

4. Split text

docs/phrasing.mdx says to name the element (the counter shows 2 items left) when the tree puts 2 and items left on different lines. A failed text or count check whose neighboring content words appear on different lines, and not as one phrase, adds hint: from SPLIT_TEXT_HINT. A spatial claim does not get the hint. The hint does not change the status.

Evidence: src/browser/split-text.test.ts and the failed-check case in src/browser/expect-advice.test.ts.

5. Security and data

  • @modelcontextprotocol/sdk is ^1.32.0 (lockfile 1.32.1).
  • webdriverio is 10.0.2. 9.32.0 did not clear the audit (extract-zip has no patched release). 10.0.2 drops that chain.
  • npm audit --omit=dev on this branch: info 0, low 0, moderate 0, high 0, critical 0.
  • docs/security.mdx covers page data sent to TypeSafe or Vercel, screenshots sent by default on a failed browser check, the 400,000-character long-context cap, $TMPDIR/plain even without --artifacts, and $VAR / ${env.*} for secrets.
  • webdriverio 10 pulls undici 8, which needs Node 22.19. The mobile CLI refuses older Node. engines stays >=22 so a browser run on 22.0–22.18 is not blocked by the package.

6. Node 22 at startup

bin/node-check.mjs exits before Playwright or webdriverio load. Browser and desktop require 22. Mobile also requires 22.19. The message names the CLI that was invoked (plain, plain-computer, or plain-mobile), the minimum, and the running version.

Evidence: scripts/node-check.test.mjs, including a spawned process on a faked Node 20.19.0 that exits 1 and does not continue.

7. Nightly live runs

.github/workflows/live.yml has cron: '0 6 * * *' (06:00 UTC). GitHub runs a schedule on the default branch only. A non-pull-request event already sets runtime=true, so the nightly run executes the checks.

The workflow already uses secrets.TYPESAFE_API_KEY. No secret was added from this environment.

8. Beta engines and the pilot runbook

The README marks desktop and mobile as beta and drops React Native from the platforms cell. The same claim is gone from docs/mobile-use.mdx and the mobile skill. docs/pilot.mdx covers about five critical browser specs in CI, a nightly schedule, and what to measure (false passes stay 0, inconclusive, retries, tokens).

9. Release 3.1.0, not published

package.json is 3.1.0. CHANGELOG.md covers the pilot items, the version 1 lock read, and the user-visible work already on main since 3.0.0. Not published. No tag. No GitHub release.

Validation

Local, on Node v22.23.3, after 0468b4c:

  • npm run verify passed in one run (/tmp/plain-verify2.log, EXIT:0). Tests 574, pass 574, fail 0. Docs ok (18). Agent notes ok (8). Source rules ok (3); the cast baseline is 62 after three ! were removed. dependency-cruiser: no violations (163 modules). Examples validate. Plugin smoke: packed npx install and MCP protocol passed for plain, plain-computer, and plain-mobile.

Not run in this environment, and why:

  • TYPESAFE_API_KEY is unset here (also unset in a fresh login shell, and there is no ~/.config/plain/.env). node scripts/e2e.mjs and node scripts/benchmark-claims.mjs --gate were not run. The previous live run on cab4bb2 is 39158122212 (E2E passed, specs 36, no-judge: 21/21, claims FALSE PASS 0). Pushing this commit starts a new Live workflow with the repository secret. That result is not in yet.
  • claude and codex are not installed (command -v finds neither), so the agent evals and routing evals that scripts/validate.mjs runs for the skill-file change were not run. CI does not run those evals.
  • Area evals against main were not run, for the same missing key. Thresholds were not lowered.

Out of scope, and not in this diff: the box primitive, comment pruning, and unrelated refactors.

Open in Web Open in Cursor 

cursoragent and others added 2 commits October 19, 2026 14:30
Pin TypeSafe to jev-1.14.0 on SDK 0.7, send an advisory failure screenshot,
retry a truncated page with long context, and stop on Node older than 22.

Co-authored-by: Gabe <gpavanello@thefork.com>
A version 1 lock still replays locators. A claim hash that does not match is judged again, and plain prints a one-line re-record notice. The long-context retry caps the uncut tree at 400k characters and does not run again on the settle re-check. plain-computer and plain-mobile name themselves in the Node check. A within claim does not get a whole-viewport screenshot line, and the split-text hint is only for text and count claims.

Co-authored-by: Gabe <gpavanello@thefork.com>
@gabe4coding
gabe4coding force-pushed the cursor/web-pilot-3-1-0-7357 branch from 0468b4c to 913432c Compare October 19, 2026 12:30
@gabe4coding
gabe4coding merged commit 3fa3e12 into main Oct 19, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants