fix(llm-agents): free the canvas bottom overlay before the memory inspect click (#1643) - #1674
fix(llm-agents): free the canvas bottom overlay before the memory inspect click (#1643)#1674Victor-w-Madeira wants to merge 7 commits into
Conversation
…tom slot (#1643) Langflow renders TWO components into one fixed container over the canvas — `absolute bottom-16 left-1/2 z-50 w-[530px] -translate-x-1/2`: - `flowBuildingComponent`, the build-status bar, transient on success (it auto-dismisses 2 s after "Flow built successfully", plus a 500 ms exit); - `UpdateAllComponents`, the "Flow needs review / N components need updates" banner, whose `shouldHide` includes `buildInfo?.success` — so it is hidden while the bar is up, takes the slot back the moment the bar dismisses, and then stays there indefinitely. That handover is why a plain wait cannot work, and it is what makes the helper worth having rather than a `waitForSelector` at each call site. The predicate is "will this occupant leave on its own?", read off the DOM as "does it offer a Dismiss button" — exactly right for both components, since the update banner always offers one and the build bar offers one only in its `buildInfo.error` state, the one state where it too never leaves. A single empty read is deliberately not enough: the slot is genuinely empty for one render tick between the bar unmounting and the banner mounting, and returning there hands the caller a click that races the banner. Two consecutive empty reads are required, and that is the load-bearing behaviour the unit test pins — reverting `EMPTY_CONFIRMATIONS` to 1 fails it. On timeout it throws naming what is still in the slot and whether Dismiss was tried, so a future build that stops honouring its own Dismiss fails attributed instead of as an unexplained `locator.click` timeout at the call site. The simulated slot in the `.fake` models the handover, the empty tick and a banner that ignores Dismiss — none of which a live spec can dwell on. Its contract was verified against nightly 1.12.0.dev39.
…pect click (#1643) Both context-id retrieval helpers waited for the "built successfully" toast and immediately clicked the Message History node's `output-inspection-messages-memory` button. Measured on nightly 1.12.0.dev39 at the default 1280x720 viewport, that button's box is y 585.6-601.0 — centre 593.3 — while the build-status bar's top edge is y 598. The click cleared it by ~5 px. The "Flow needs review" banner that replaces the bar is 12 px taller, top edge y ~586, i.e. ABOVE the centre, so the identical click was refused for as long as the banner owned the slot — and the banner never leaves on its own, which is why all 20 s of `locator.click` retries were consumed on all three attempts of both specs on the 2026-08-31 daily (run 33410643882). Reproduced deterministically on the nightly by dwelling 4 s before the click, which hands the slot to the banner: the failure comes back with the interceptor byte-identical to the daily's call log — `<div class="flex items-center justify-between gap-6 rounded-lg border bg-background px-4 py-3 text-sm shadow-md">`. With `clearCanvasBottomOverlay` in front of it, the same dwell passes: the banner is dismissed, the slot reads EMPTY, and the second retrieval finds it still empty because Langflow keeps the dismissal in `dismissedNodes`. The banner is there because these specs seed their flow from `tests/assets/flows/chat-io-ok-trace-fixture.json`, whose nodes carry `lf_version: 1.7.0`; the 1.12 nightly reports one of them as outdated ("1 component needs updates", confirmed live). Refreshing that fixture was rejected as the fix: it is shared by many specs, it would silence this only until the next upstream template bump, and it would leave the build bar's ~5 px margin exactly as fragile. `click({ force: true })` was rejected too — force skips the actionability check but the browser still dispatches at the point, so the overlay would swallow the click and the test would fail later, worse attributed. No assertion changed: what each test proves about context-scoped retrieval is untouched.
…th retrieval tests (#1643) PR #1647 removed `@stable` AND added `test.fixme` by hand at the 2026-08-31 triage, because the mass-failure guard tripped (10 hard failures > 5) so the workflow auto-removed nothing, while the day's verdict judged these two failures non-environmental — their final attempt ran with the backend measured at 0-3% of probes down. With the overlay interception fixed and the cause named, both come back: - `agent-context-id-isolation.spec.ts` — "mirrored context-scoped retrievals return only their own context's messages" - `agent-context-id-continuity.spec.ts` — "context-scoped retrieval returns all turns of the context and not the untagged control" Both files are `test.describe.configure({ mode: "serial" })`, so each failure had also been skipping its file's sibling — 2 of that run's 4 genuine skips. Verified from the runner rather than from the diff: a full-file run of both spec files is 6 passed / 2 skipped, the two skips being the anthropic parametrized targets, which `providers.json` records as inactive on a drained key ("Your credit balance is too low"), unrelated to this issue.
…context-id spec docs (#1643) Adds the measured mechanism to *Step by step* (the shared container, which component owns the slot when, the ~5 px margin and the 12 px height difference that decides the click, and why refreshing the seeded fixture was not the fix), names the new slot-clearing step in the retrieval sequence, records the quarantine lift under *Tags*, and moves *Last validated* to nightly 1.12.0.dev39.
There was a problem hiding this comment.
🟢 Approval recommended
The fix is localized, restores quarantined coverage safely, and includes targeted unit tests that pin the previously flaky handover edge case.
Pull request overview
This PR fixes a deterministic E2E test defect where Langflow’s canvas bottom overlay intermittently (and sometimes permanently) intercepted pointer events, causing the context-id retrieval specs to hard-fail on the memory-output inspect click. The change adds a shared UI helper to proactively clear that overlay slot before clicking, lifts the prior quarantine by restoring @stable and removing test.fixme, and updates the associated spec docs accordingly.
Changes:
- Add
clearCanvasBottomOverlay(page)helper (with unit tests + a fake overlay timeline) to wait out transient overlays and dismiss persistent ones. - Call the helper in both context-id retrieval helpers immediately before clicking
output-inspection-messages-memory. - Restore
@stableand remove the quarantinetest.fixmein both specs; update spec docs’ “Last validated” and add the overlay note.
File summaries
| File | Description |
|---|---|
| tests/tests-automations/regression/core-functionality/llm-agents/agent-context-id-isolation.spec.ts | Uses the new overlay-clearing helper before the memory inspector click; restores @stable and removes quarantine skip. |
| tests/tests-automations/regression/core-functionality/llm-agents/agent-context-id-continuity.spec.ts | Same overlay-clearing integration before the inspect click; restores @stable and removes quarantine skip. |
| tests/helpers/ui/clear-canvas-bottom-overlay.ts | New UI helper that polls the shared bottom overlay slot, dismissing when possible and failing with an attributed error if it can’t clear. |
| tests/helpers/ui/clear-canvas-bottom-overlay.test.ts | Unit coverage for the helper’s key behaviors, including the one-tick empty handover edge case and “dismiss no longer works” attribution. |
| tests/helpers/ui/clear-canvas-bottom-overlay.fake.ts | Fake Page/Locator implementation to deterministically simulate the overlay handover timeline and non-dismissible variants. |
| docs/core-functionality/llm-agents/agent-context-id-isolation.md | Updates “Last validated” and documents the overlay mechanism + the new required step (freeing the slot) before inspection. |
| docs/core-functionality/llm-agents/agent-context-id-continuity.md | Same as isolation doc: updates “Last validated” and adds the overlay note + procedural step. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ector, a failed build and a wider handover gap (#1643) Three defects found in review of the first version, all confirmed against the Langflow source and a live nightly. **It was fail-OPEN on selector drift, which contradicted its own attribution promise.** The docstring claimed an overlay change fails attributed; that held for a non-dismissible overlay but not for the likelier upstream edit — a Tailwind change (`w-[530px]` -> `w-[560px]`, `bottom-16` -> `bottom-20`). The selector would then match nothing, the helper would report the slot clear while the overlay is fully present, and #1643 would come back as exactly the unattributed 20 s `locator.click` timeout — now with a helper call in the trace implying it was handled. Every caller reaches this right after `waitForSelector("text=built successfully")`, which proves the bar is on screen, so "nothing ever matched" can only mean the selector is lost. It now throws, with `{ allowAlreadyClear: true }` for a future caller that may legitimately start free. The upstream files are also declared in both spec docs' `## External dependencies`, so `watch-upstream-areas.mjs --mode=check-docs` fails the PR that renames them. **The empty-slot window was sized against the wrong constant.** The code said "one render tick" and the doc said "a 500 ms exit"; neither is the mechanism. `handleDismiss` sets `dismissed = true` — the bar leaves via a framer-motion exit (`duration: 0.2, delay: 0.2`, ~340 ms measured) — and SEPARATELY schedules `setBuildInfo(null)` at +500 ms, which is what un-hides the banner. The gap is `500 ms - exitAnimation`, measured at ~89 ms on dev39 with a 10 ms sampler. Two reads 200 ms apart cleared that by ~2.2x, but the ceiling is upstream-controlled: drop the exit animation and the gap walks to the full 500 ms, at which point the whole window fits inside it and the helper returns into the banner's mount. `EMPTY_CONFIRMATIONS` is now 5 (an 800 ms quiet window) and `BANNER_UNHIDE_DELAY_MS` records the real constant. The fake counts ticks and ignores durations, so no behavioural test can see this — `POLL_MS = 0` survived all six — so the relationship is asserted on the constants instead. **The failed-build bar satisfied the "offers a Dismiss" predicate.** It renders Retry + Dismiss and has no timer, so it is genuinely an occupant that will not leave — but dismissing it erases the only UI evidence of a failed node run, and `flow-error-policy`'s v2 verdict is advisory on 1.12.x, so a future caller could go green on a build that failed. It is now refused by name. Unreachable for today's two callers (`flowBuild.builtSuccessfully` is the only rendered "built successfully" string, so their wait proves `buildInfo.success`) — but the guarantee lived in the callers, not in the helper. Also documents a side effect the first version did not: dismissing the update banner is not read-only. `handleDismissAllComponents` calls `setNodes` marking each flagged node `edited: true`, and the editor persists it with a `PATCH /api/v1/flows/{id}` (observed on the wire). Harmless for a caller that seeds a throwaway flow and deletes it in teardown; a trap for one that asserts on the persisted graph or counts flow writes. Unit tests go 6 -> 9.
…ate on 1.13.0.dev0 (#1643) Three corrections from review. `## External dependencies` now names the two frontend components the retrieval step reads, so the repo's own rename guard covers them. `Last validated` moves to **1.13.0.dev0**. Two reasons. The continuity doc had gone BACKWARDS — #1187 recorded `1.12.0.dev45` four commits ago and the first version of this PR overwrote it with the older `dev39`. And `langflowai/langflow-nightly:latest` cut over to the 1.13 cycle on 2026-09-02T03:54Z, which is the image `daily-stable.yml` pulls — so restoring `@stable` on a dev39-only validation would ship these tests into a lane running an image they were never run against. Both specs re-validated there: 2 clean `--retries=0` full-file runs, 6 passed / 2 skipped (the anthropic targets, provider `inactive` on a drained key). The overlay mechanism is unchanged on 1.13 — and the run proves it rather than assuming it, since the helper now fails closed when its selector matches nothing. The `mode: "serial"` half of the issue title gets the reasoning it was missing: the mode stays (agent-area rule), and the coupling is already mitigated by declaring the model-free describe before the parametrized loop — an ordering that cannot help when the failure is in the FIRST describe, which is what happened here.
…-limit too (#1643) The third file of the cluster. It seeds from the same `chat-io-ok-trace-fixture.json`, adds the Message History node the same way (`addComponentFromSidebar` + `adjustScreenView({numberOfZoomOut: 0})`), and reaches `output-inspection-messages-memory` with the same five lines the other two had — and both of its tests are `@stable`, so they run in the daily. The #989 precedent treated all three sibling files in one PR. **This is hardening, not a fix of a live defect here, and the difference was measured rather than assumed.** The banner IS present on this spec's flow too (nightly 1.13.0.dev0, slot y 586-656, x 515-1045, "Flow needs review / 1 component needs updates"). What differs is the margin: this node exposes two advanced fields where the context-id specs expose three, so it is shorter and its inspect button lands at y 541.0-556.9 — **~37 px clear of the banner's top edge**, against ~5 px on the other two. Forcing the failing condition (dwelling 4 s so the banner owns the slot) does NOT reproduce an interception here; it was checked, and it passed. Added anyway because 37 px of clearance is a coincidence of node height, and node height is exactly what upstream moves — `dev46` already changed how these fields are exposed. The step costs ~2 s per retrieval and removes the dependence. Validated on nightly 1.13.0.dev0: 2 clean `--retries=0` runs (2 passed, ~18 s), no `🚨 Backend Error`. Force-fails executed on both tests — truncation asserting 3 instead of 2 fails (`Expected: 3, Received: 2`), causal control asserting `SEEDED_MESSAGES + 1` fails (`Expected: 11, Received: 10`) — and reverted, 0 mutation markers left.
Closes #1643.
Problem
Both context-id retrieval specs hard-failed all 3 attempts of the 2026-08-31 daily (run 33410643882, triage #1642) on a 20 s
locator.clicktimeout againstoutput-inspection-messages-memory— the button resolving asvisible, enabled and stableand every retry refused by<div class="absolute bottom-16 left-1/2 z-50 w-[530px]">… subtree intercepts pointer events. Quarantined by hand in #1647 because the mass-failure guard tripped and the workflow auto-removed nothing.Root cause (test defect — confirmed live, not a product regression). Langflow renders two different components into one fixed container over the canvas,
absolute bottom-16 left-1/2 z-50 w-[530px] -translate-x-1/2:flowBuildingComponent— the build-status barhandleDismissfires 2 s after "Flow built successfully"UpdateAllComponents— the "Flow needs review / N components need updates" bannershouldHideincludesbuildInfo?.success, so it is hidden while the bar is up, retakes the slot when the bar's state is cleared, and then never leavesMeasured at the default 1280×720 viewport, the inspect button's box is y 585.6–601.0, centre 593.3. The build bar's top edge is y 598 — the click clears it by ~5 px. The banner is 12 px taller, top edge y ~586 — above the centre. So the identical click passed or was refused purely on which component owned the slot, and once the banner owned it no amount of retrying could help, which is exactly what the call log shows: first interceptor the bar's inner
flex min-h-10 w-full items-center justify-between gap-2, second the banner'sflex items-center justify-between gap-6 rounded-lg border bg-background px-4 py-3 text-sm shadow-md.The banner is present because these specs seed their flow from
tests/assets/flows/chat-io-ok-trace-fixture.json, whose nodes carrylf_version: 1.7.0; the nightly reports one of them as outdated.Reproduced deterministically: dwelling 4 s before the click hands the slot to the banner, and the failure returns with the interceptor byte-identical to the daily's. With the fix the same dwell passes — probe reads
Flow needs review …→EMPTY.The environmental hypothesis is ruled out as the issue asked: this reproduces on an idle local nightly with no wedge, and the daily's own liveness recorder had both specs' final attempt at 0–3 % of probes down.
Fix
New shared helper
tests/helpers/ui/clear-canvas-bottom-overlay.ts, called between the run and the inspect click. It frees the slot instead of clicking into it, and fails closed in three directions (the second and third came out of review):locator.clicktimeout at the call site.waitForSelector("text=built successfully"), which proves the bar is on screen, so "nothing ever matched" can only mean an upstream Tailwind edit (w-[530px]→w-[560px]). Reporting "clear" there would bring [Daily #1642] agent-context-id-{isolation,continuity} — an overlay at the canvas bottom intercepts the memory-output inspect click, and mode: 'serial' skips each file's sibling #1643 back as precisely the unattributed timeout the helper exists to prevent — with a helper call in the trace implying otherwise.{ allowAlreadyClear: true }opts out.flow-error-policy's v2 verdict is advisory on 1.12.x, so a caller could go green on a build that failed.The empty-slot window is sized against the real constant, not the observed gap.
handleDismisssetsdismissed = true(framer exit ~340 ms) and separately schedulessetBuildInfo(null)at +500 ms, which is what un-hides the banner — so the gap is500 ms − exit, measured ~89 ms with a 10 ms sampler. Two 200 ms-spaced reads cleared that by ~2.2×, but drop the exit animation upstream and the window fits inside the gap.EMPTY_CONFIRMATIONS = 5(an 800 ms quiet window), and since the fake counts ticks and ignores durations (aPOLL_MS = 0survived every behavioural test), the relationshipPOLL_MS × (EMPTY_CONFIRMATIONS − 1) > BANNER_UNHIDE_DELAY_MSis asserted on the constants.Also documented, because it was not obvious: the helper writes to the flow.
handleDismissAllComponentscallssetNodesmarking each flagged nodeedited: true, and the editor persists it with aPATCH /api/v1/flows/{id}(observed on the wire). Harmless for a caller that seeds a throwaway flow and deletes it in teardown; a trap for one that asserts on the persisted graph.Rejected alternatives: refreshing
chat-io-ok-trace-fixture.json(shared by many specs, silences this only until the next template bump, leaves the build bar's ~5 px margin as fragile);click({ force: true })(force skips the actionability check but the browser still dispatches at the point — the overlay swallows it and the test fails later, worse attributed); panning/zooming the node out of the slot (trades one geometric coincidence for another).Third file:
agent-n-messages-limit.spec.ts— hardening, and the distinction is measuredSame fixture, same node, same five lines, both tests
@stable; #989 treated the three sibling files together. But it is not currently intercepted, and that was checked rather than assumed. The banner is present on its flow (1.13.0.dev0, slot y 586–656) — what differs is the margin: this node exposes two advanced fields where the context-id specs expose three, so it is shorter and its inspect button sits at y 541.0–556.9, ~37 px clear against ~5 px. Forcing the failing condition does not reproduce an interception there. Added anyway because 37 px is a coincidence of node height and node height is exactly what upstream moves (dev46already changed how these fields are exposed); it costs ~2 s and removes the dependence.Four further specs share the exposure in a different shape (
split-text-chunking, and byte-identical privatedismissUpdateBannerIfPresentcopies inrag-pipeline/vector-store-index-query, plus a barewaitForTimeout(600)inchatInputOutputUser-shard-1). Folding those into the helper is a dedup refactor, not this fix — follow-up issue.Scope note
No assertion changed anywhere. What each test proves is untouched; only the path to the output inspector is. The parametrized agent tests in these files are unchanged.
All three files stay
mode: "serial"(the agent-area rule). With the retrieval tests passing, each file's sibling runs again — the 2 skips this cluster caused are gone, verified from the runner. The coupling itself is already mitigated the way these files can mitigate it: the model-free describe is declared before the parametrized loop, so a weak-model failure cannot skip the half that needs no provider. That ordering cannot help when the failure is in the first describe, which is what happened here.Validation
Run on nightly
1.13.0.dev0—langflowai/langflow-nightly:latestcut over to the 1.13 cycle on 2026-09-02T03:54Z, and that is the imagedaily-stable.ymlpulls, so restoring@stableon a 1.12-only validation would ship these into a lane running an image they were never run against. Root cause and geometry were measured on1.12.0.dev39.npm run typecheck✅ ·npm run lint✅ (0 errors) ·npm run test:units✅ (893 pass / 0 fail, incl. 9 new) ·npm run check:checklist-coverage✅ ·check-checklist-guard.mjs✅ ·watch-upstream-areas --mode=check-docs✅ (no unresolved path in the changed docs)6 passed, 2 skipped(1.6–3.8 min). 2 clean runs ofagent-n-messages-limit:2 passed(~18 s).--retries=0runs of the isolation retrieval test.providers.jsonrecords the providerinactive("Your credit balance is too low to access the Anthropic API"), a pre-existing key state unrelated to this issue. openai and google pass.mainand passes with the fix.🚨 Backend Erroron the final runs. (An earlier 1.12 run logged one404onGET /api/v1/flows/{id}— that isresolveLiveFlowIdprobing the collected transient ids on purpose, documented in the spec.)--trace=onnot run: known limitation on the Simple Agent template family (Create agent-tool-name-validation.spec.ts — invalid tool name blocked with clear message #490/flaky/bug: export-import-flow export produces empty flow (nodes: []) — regression of #384 #518), where tracing hangs indefinitely. Covered by the--retries=0bursts, the force-fails and live scout probes instead.Force-fails — all EXECUTED and observed failing, then reverted (0 mutation markers left)
agent-context-id-isolationretrievalExpected substring: "CTXISO-…-B-"⇒ failedagent-context-id-continuityretrievalExpected substring: "CTXCONT-…-CTRL"⇒ failedagent-context-id-isolationparametrizedmessage(s) with wrong context_id: […]⇒ failedagent-context-id-continuityparametrizedEMPTY_CONFIRMATIONS2 → 1agent-n-messages-limittruncationExpected: 3, Received: 2⇒ failedagent-n-messages-limitcausal controlSEEDED_MESSAGES + 1Expected: 11, Received: 10⇒ failedRelated: #1642 (triage), #1647 (quarantine), #989 (the previous fix of this same three-file cluster).