fix(cli, react): honour --json on react/vitals; classify opaque-Promise client suspensions as client-hook#1350
Open
gaojude wants to merge 2 commits into
Open
Conversation
`--json` is registered as a global boolean flag in `clean_args`, so it's
stripped from the args before `parse_command` sees them. The per-command
parsers for `react <sub>` and `vitals` then check `rest.contains("--json")`
on the already-stripped args and silently drop the flag — `cmd.json` is
never set, so the action returns the formatted `report` instead of the
raw `boundaries` payload.
Read `flags.json` (populated by `parse_flags` from the pre-strip args) in
both parsers. The `rest.contains` check is kept as a fallback so the
existing unit tests that call `parse_command` directly with `--json` in
the args slice continue to work.
Regression test asserts that with `flags.json = true` and `--json`
absent from `rest` (the real call path), all four react subcommands
emit `cmd.json = true`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A client component using `use(somePromise)` where the promise is an opaque user-created `Promise` (e.g. `fetch().then(r => r.json())` stashed in `useState`) was landing on `BlockerKind::Unknown`. React reports the suspender as `awaited.name = "Promise"` with no other identifying signal in the name, so none of the existing name-based checks fire — yet this is the prototypical "client-hook" scenario the classifier advertises. The discriminator is `env`: React tags server-tracked awaits with `env="Server"` (the suspenders named "rsc stream", "_Response.json", anonymous fetches, etc. that come back from RSC), while client-side suspensions have `env` unset or "Client". Reclassify opaque-Promise suspenders that aren't explicitly server-side as `ClientHook`. Verified against a Next.js App Router demo with two Suspense boundaries: a server component awaiting `fetch()` (still classified as `Stream` / `ServerFetch` by name) and a client component awaiting `use(promise)` (was `Unknown`, now `ClientHook`). Tests cover: client-side opaque Promise -> ClientHook, explicit `env="Client"` Promise -> ClientHook, `env="Server"` Promise -> Unknown (no regression on server awaits), and existing named-hook handling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
@gaojude is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two small fixes for the React features shipped in #1257. Pushed as draft so we can iterate on the description / scope.
1.
--jsonis silently stripped fromreactandvitalssubcommandsFile:
cli/src/commands.rs--jsonis registered as a global boolean inclean_args'sGLOBAL_BOOL_FLAGS, so it's removed from the args slice beforeparse_commandsees them. The per-command parsers forreact <sub>andvitalsthen checkrest.contains(\"--json\")on the already-stripped args, so the flag silently no-ops and the action returns the formattedreportinstead of the raw structured payload (boundariesfor suspense, the inspector JSON for the others).The fix reads
flags.json— whichparse_flagspopulates from the pre-strip args — in both parsers. The existingrest.containscheck is kept as a fallback so the unit tests that callparse_commanddirectly with--jsonin the args slice continue to work.Repro before the fix:
```shell
$ agent-browser react suspense --json | jq 'keys'
[
"report" # expected: "boundaries"
]
```
A regression test asserts that with `flags.json = true` and `--json` absent from `rest` (the real call path), all four react subcommands emit `cmd.json = true`.
2. `use(opaquePromise)` in a client component lands on `Unknown` instead of `ClientHook`
File: `cli/src/native/react/suspense.rs`
A client component calling `use(somePromise)` where the promise is an opaque user-created `Promise` (e.g. `fetch().then(r => r.json())` stashed in `useState`) was classified as `BlockerKind::Unknown`. React reports the suspender as `awaited.name = "Promise"` with no other identifying signal in the name, so none of the existing name-based checks fire — yet this is the prototypical "client-hook" scenario the classifier advertises.
The discriminator I landed on is `env`. React tags server-tracked awaits with `env="Server"` (the suspenders named `rsc stream`, `_Response.json`, anonymous fetches, etc. that come back from RSC), while client-side suspensions arrive with `env` unset or `"Client"`. Adding a fallback that reclassifies opaque-Promise suspenders as `ClientHook` when `env` is not explicitly `"Server"` resolves the case without stealing classifications from server-side awaits.
Verified against a Next.js App Router demo with two Suspense boundaries:
Tests cover: client-side opaque Promise → ClientHook, explicit `env="Client"` Promise → ClientHook, `env="Server"` Promise → Unknown (no regression on server awaits), and existing named-hook handling.
Test plan
Out of scope
While verifying, I noticed that `agent-browser react suspense` (no `--json`) prints `✓ Done` instead of the formatted report — the daemon returns `{"report": "..."}` but the response printer doesn't render the `report` key for these subcommands. Separate bug; happy to follow up if you'd like it in this PR or a separate one.
🤖 Generated with Claude Code