Skip to content

[fix] Stop the Use API drawer render loop with two variants [#6708] - #6711

Merged
mmabrouk merged 2 commits into
release/v0.115.3from
fix/release-1153-use-api-drawer-loop
Sep 9, 2026
Merged

mmabrouk merged 2 commits into
release/v0.115.3from
fix/release-1153-use-api-drawer-loop

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Closes #6708

Context

Open an app that has more than one variant, go to the Variants registry, select a row and click Use API. The "How to use API" drawer enters an infinite React render loop the moment it opens. The snippet flips between the two variants many times a second. Click any language tab and the loop turns synchronous: React throws #185 continuously and the tab is frozen until it is reloaded. The drawer opened from the Deployments tab is not affected. Issue: #6708

VariantUseApiContent kept two pieces of state that must agree, selectedVariantId and selectedRevisionId, and reconciled them with three effects that each wrote what another read. The three effects run in one pass, so each one sees a snapshot where the others have not applied yet, and each re-asserts its own view. With one variant all three agree and it settles. With two variants the branch initialRevision.workflow_variant_id !== selectedVariantId becomes reachable and they oscillate.

Here is the cycle, logged from the reproduction:

AGEFFECT A-fallback var-default                     # no revision known yet, pick variants[0]
AGEFFECT A-init     var-second rev-second-1         # the opened revision resolves, adopt its variant
AGEFFECT B variant=var-default -> rev=rev-default-2 # same pass, stale variant, adopt ITS latest revision
AGEFFECT C rev=rev-default-2 -> variant=var-default # that revision belongs to the other variant
AGEFFECT A-init     var-second rev-second-1         # and back

This is not a v0.115.3 regression. The three effects have been in the file since March 2026 and last changed in May 2026. What is new is the acceptance test's drawer wait, which made the freeze reachable in CI instead of failing earlier on a stability check.

Changes

The revision is now the authoritative selection. The parent variant is derived from it and never stored.

Before, the variant came from a workflow-wide revision list that arrives late, and three effects pushed the two states at each other:

useState selectedVariantId + useState selectedRevisionId
effect A: initialRevision -> write BOTH
effect B: selectedVariantId + variantRevisions -> write revision
effect C: revisionList + selectedRevisionId -> write variant

After, the drawer reads the selected revision entity, which already carries workflow_variant_id, so nothing has to arrive before the drawer knows which variant it is showing:

useState selectedRevisionId          (initialised from the prop)
const selectedVariantId = selectedRevision?.workflow_variant_id ?? <fallback>
effect: if there is no usable selection, adopt the latest revision

Two writers remain and neither can fight the other. One is keyed on the initialRevisionId prop. The other runs only while there is no usable selection, and it writes through a functional update so it cannot overwrite a revision the prop supplied in the same pass.

The drawer also tells "still fetching" apart from "this revision does not exist". A stale id, for example a deleted revision behind a shared link, now falls back to the first variant's latest revision instead of spinning for ever.

Two smaller consequences. The drawer no longer mounts the workflow-wide revisions query, and it reads the thin revision refs for the variant rather than resolving every revision entity, so it opens with fewer queries than before. While the opened revision is still being fetched the drawer shows its spinner.

Tests

  • New unit test VariantUseApiContent.loop.test.tsx mounts the drawer content against the real workflow atoms and real react-query, with two variants and the arrival order that opens the race. On the old component it records 62 renders alternating myapp.default and myapp.e2e and React reports "Maximum update depth exceeded". On the new component the trajectory is two renders, it never builds a snippet for the variant the user did not open, and rendering stops once the queries resolve. Four cases: the loop, the drawer opened without a revision, a revision the prop supplies in the same pass as the fallback list, and a revision that does not exist. The last two fail on the first pass of this fix and pass now.
  • pnpm exec tsc --noEmit in web/oss: 0 errors. pnpm run lint-fix in web/oss: clean. Full web/oss unit suite: 62 files, 531 tests, all pass.
  • Acceptance pair run three times against a local EE stack serving this branch, --workers=1 --retries=0. "Registry: use API snippets > should show variant TypeScript snippet" passed all three times, in 5.2 s, 3.4 s and 3.2 s. It used to hang for the full 60 s timeout. The one failure in each run is "Should run single view variant for chat", which times out waiting for an LLM response because that stack has no provider key. It is unrelated and fails identically on every run. Both use-API tests passed again on the final code.
  • Live browser check on the same stack, eleven attempts on this branch: 0 DOM mutations in the drawer over 3 s, the variant slug identical across all 12 samples, the TypeScript tab selected in about 0.2 s, and zero React docs: add SamMethnani as a contributor for doc #185 errors.
  • I could not reproduce the old loop in the browser on that stack. Ten attempts on the release code, including runs that delay the workflow-wide revisions request, all stayed stable. The registry page warms the revision cache before the drawer opens, which closes the race. The issue reports 3 of 5 on staging, so this is consistent. The deterministic reproduction is the unit test.

PR #6709 quarantines the acceptance test for this bug. It is still open and not merged, so there is no fixme on release/v0.115.3 to revert. Close #6709 rather than merging it.

A Codex review at xhigh found the two selection defects listed above, which are now fixed and covered. It also reported a separate pre-existing oscillation in TraceDrawerContent.tsx in the observability package, where a local selected span and the global activeSpanId copy into each other. That is not this component and not these atoms, so it is out of scope here and worth its own issue.

What to QA

  • Open an app with two or more variants, go to Variants, select a row, click Use API. The snippet names the variant of the row you selected and does not change on its own.
  • Click the TypeScript tab and then the cURL tab. Both respond at once and the page stays interactive.
  • Change the variant with the picker inside the drawer. The snippet follows the picker, and the version tag next to it matches.
  • Regression: open Use API from the Deployments tab. It behaves as before.
  • Regression: open the drawer on an app with a single variant. The snippet names that variant.

The variant-mode "How to use API" drawer kept a variant id and a revision id
in state and reconciled them with three effects that each wrote what another
read. With more than one variant the reconciliation had no fixed point: the
drawer alternated between the two variants forever, and the first click on a
language tab turned the loop synchronous and froze the tab with React #185.

Make the revision the only selection state and derive the variant from it.
The variant now comes from the selected revision entity, which already carries
workflow_variant_id, so no list has to arrive before the drawer knows which
variant it is showing. One effect remains, and it writes only while nothing is
selected, so it runs at most once.

Fixes #6708
@mmabrouk

mmabrouk commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@vercel

vercel Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agenta-documentation Ready Ready Preview Sep 9, 2026 12:51am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Fixed repeated re-rendering in the “How to use API” drawer when an app has multiple variants.
    • Improved variant and revision selection so API snippets consistently reflect the selected revision or the latest available revision.
    • Improved loading and recovery when revision or variant information is delayed or unavailable.
    • Preserved explicitly selected revisions even when variant information arrives later.

Walkthrough

VariantUseApiContent now derives variant data from the selected revision and uses one-way revision initialization. Regression tests cover delayed queries, multiple variants, explicit revision selection, and latest-revision fallback.

Changes

Variant API drawer

Layer / File(s) Summary
Revision-driven selection
web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx
The component stores the selected revision ID, derives the variant from that revision, applies a latest-revision fallback, and handles delayed variant or revision data.
Multi-variant regression coverage
web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx
The test mocks query timing and leaf components, then verifies stable snippets for explicit revisions and default-variant fallback revisions.

Priority: ⬆️ High

Estimated code review effort: 3 (Moderate) | ~20 minutes

Severity of issue fixed: High

Merge Risk: 🔵 Low · up to d76cc

The drawer now uses revision-driven selection to prevent multi-variant render loops and includes regression coverage for delayed and missing revisions. The remaining risk is limited to a potentially brittle test assertion that could cause intermittent CI failures without affecting runtime drawer behavior.

Sequence Diagram(s)

sequenceDiagram
  participant VariantUseApiContent
  participant WorkflowMolecule
  participant QueryAPI
  participant LanguageCodeBlock
  VariantUseApiContent->>WorkflowMolecule: Read selected revision
  VariantUseApiContent->>QueryAPI: Load variants and revisions
  QueryAPI-->>VariantUseApiContent: Return revision and variant data
  VariantUseApiContent->>VariantUseApiContent: Derive selected variant
  VariantUseApiContent->>LanguageCodeBlock: Render selected variant snippet
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The implementation fixes the underlying product bug, but linked issue [#6709] specifically requires quarantining the affected acceptance test with test.fixme. This PR does not implement that requireme… Either add the required test.fixme quarantine from [#6709], or update the linked issue and link the product-fix issue [#6708] so the revised scope explicitly authorizes the implementation change.
Out of Scope Changes check ⚠️ Warning The product-code refactor and new unit test are outside the explicit scope of [#6709], which requests only an acceptance-test quarantine. The changes may be valid for [#6708], but that issue is not di… Link [#6708] or update [#6709] to include the product fix and unit-test scope, or remove the out-of-scope source and unit-test changes and submit only the requested quarantine.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Title check ✅ Passed The title clearly identifies the main fix: stopping the Use API drawer render loop for applications with two variants. It is concise and specific.
Description check ✅ Passed The description directly explains the render-loop bug, the implementation changes, test coverage, validation results, and QA scenarios.
Full details: Linked Issues check

Explanation

The implementation fixes the underlying product bug, but linked issue [#6709] specifically requires quarantining the affected acceptance test with test.fixme. This PR does not implement that requirement.

Full details: Out of Scope Changes check

Explanation

The product-code refactor and new unit test are outside the explicit scope of [#6709], which requests only an acceptance-test quarantine. The changes may be valid for [#6708], but that issue is not directly linked here.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-1153-use-api-drawer-loop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx (1)

40-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Shorten the added comments to one line each.

The added blocks use three or four lines per comment. The coding guidelines allow at most one short line per comment. Move the rationale for issue #6708 into the PR description or the test file header, and keep one line here.

♻️ Proposed comment trimming
-    // The revision is the ONLY selection state here; the variant is DERIVED from it and is
-    // never stored. Two states that must agree cannot be reconciled by effects that each
-    // write what another reads: with more than one variant that reconciliation had no fixed
-    // point and the drawer re-rendered forever (issue `#6708`).
+    // Revision is the only selection state; the variant is derived (issue `#6708`).
     const [selectedRevisionId, setSelectedRevisionId] = useState<string | undefined>(
         initialRevisionId,
     )
-    // The drawer is destroyed when it closes, so this only fires when the host swaps the
-    // opened row while the drawer stays mounted. It is keyed on the prop, not on derived
-    // state, so it writes once per prop value.
+    // Adopts a new opened revision while the drawer stays mounted.
     useEffect(() => {
-    // With no revision selected yet the drawer defaults to the first variant, and the effect
-    // below then adopts that variant's latest revision.
+    // No revision selected yet: default to the first variant.
     const selectedVariantId = derivedVariantId ?? (selectedRevisionId ? undefined : variants[0]?.id)

As per coding guidelines: "Hard rule. At most ONE short line per comment."

Also applies to: 50-52, 94-95

Source: Coding guidelines

web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx (1)

216-216: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider relaxing the exact commit budget.

MAX_COMMITS at line 122 already fails the test on a render loop. The extra toBeLessThanOrEqual(6) bound asserts an exact commit count, so any benign added render — a new atom subscription, for example — fails the test without a loop. Raise the bound or drop it and keep the final-slug assertions.

♻️ Proposed change
-    expect(trajectory.length).toBeLessThanOrEqual(6)
+    expect(trajectory.length).toBeLessThan(MAX_COMMITS)

Also applies to: 230-230


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: 309ada7d-e749-4da1-8ed0-e5f38527f3c7

📥 Commits

Reviewing files that changed from the base of the PR and between c181101 and 8eb8fe2.

📒 Files selected for processing (2)
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-09-09T01:05:33.116Z

…rable

Two defects Codex found in the first pass of the fix.

The prop effect and the fallback effect could both see an empty selection in
one pass, so the fallback overwrote a revision the prop had just supplied. The
fallback now writes through a functional update that keeps whatever is there.

A revision id that resolves to nothing, a deleted revision behind a shared
link, left the drawer spinning for ever. Read the revision query so pending is
told apart from unresolvable, and fall back to the first variant's latest
revision when the requested one cannot be resolved.

Also read the thin revision refs for the variant instead of resolving every
revision entity, and accept the variant_slug alias the schema allows.
@mmabrouk

mmabrouk commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Pushed two follow-up fixes from a Codex xhigh review, with tests that fail on the previous commit. Re-review please @coderabbitai

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx (1)

40-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the added comments to one short line each.

The coding guidelines allow at most one short line per comment. The added blocks at Lines 40-43, 97-98, 109-110, 115-116, 129-130 and 144-145 each use several lines of explanation. Move the reasoning to the PR description or the issue, and keep one line at each site.

As per coding guidelines: "Hard rule. At most ONE short line per comment."

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: ad42f5ac-5ab7-4a6e-85d6-7e9b6d8d47ae

📥 Commits

Reviewing files that changed from the base of the PR and between 8eb8fe2 and d76cc03.

📒 Files selected for processing (2)
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

@mmabrouk I will perform a full review of the updated changeset.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx (1)

40-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce each inline comment to one short line. The changed code adds multi-line explanatory comments. The coding guidelines set a hard rule of at most one short line per comment. Move the longer rationale to the PR description or the issue link.

  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L40-L43: replace the four-line rationale with one line, for example // Revision is authoritative; variant is derived (issue #6708).
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L50-L52: reduce to one line describing the prop-keyed initialization.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L82-L83: reduce to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L97-L98: reduce to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L109-L110: reduce to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L115-L116: reduce to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L129-L130: reduce to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx#L144-L145: reduce to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx#L76-L81: reduce the block comment to one line.
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx#L144-L147: reduce the block comment to one line.

As per coding guidelines: "Hard rule. At most ONE short line per comment."

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: 9185895a-c0ef-4a1d-8e7c-20b951e2ebfb

📥 Commits

Reviewing files that changed from the base of the PR and between c181101 and d76cc03.

📒 Files selected for processing (2)
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.loop.test.tsx
  • web/oss/src/components/DeploymentsDashboard/assets/VariantUseApiContent.tsx

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.

@mmabrouk
mmabrouk merged commit 7f4ec82 into release/v0.115.3 Sep 9, 2026
61 of 65 checks passed

This branch was successfully deployed

1 active deployment
Preview — d76cc034 Deployed Sep 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant