The e2e backend runs the framework's runtime shim, like the framework does - #185
Merged
Merged
Conversation
… does
Six ports have been 500ing on their first roundtrip - 049, 121, 241, 291, 299,
308 - with
ASSERTION_FAILED @ lcl_heap.add_object (kernel_call_transformation)
and nothing naming a class or an attribute. The nightly has reported them for
over a week (#162, #172, #173), and the weekly pin bump then read as "the
framework broke six ports" (#181). It did not. This build was not running a
shim the framework has been shipping since abap2UI5#2707.
The draft is written with CALL TRANSFORMATION id, whose transpiled
re-implementation walks a serializable object attribute by attribute through a
dynamic ASSIGN obj->( name ). A PRIVATE ABAP attribute is a JavaScript #field
in the transpiled class, which no name lookup reaches - sy-subrc is 4 and the
writer's ASSERT dies. A real system serializes private attributes perfectly
well, so nothing else can catch it: not ABAP, not abaplint, not the transpiler.
All six ports bind with omit_initial_paths, which makes the client hand in
lcl_initial_paths_filter - a framework class, serializable by design, whose
mt_names is private. Nothing in the corpus was wrong.
abap2UI5 knows this: node/setup/patch-abaplint-runtime-assign.mjs, block 2,
falls back to the transpiler's FRIENDS_ACCESS_INSTANCE, and the framework runs
that script before every one of its own transpiled runs (auto_transpile, unit,
express). e2e-build already runs its downport sibling and simply never ran this
one, so the backend served here had the unpatched runtime.
It runs it now, next to the other, and guarded: a pin older than #2707 has no
such script and the build says so instead of failing.
Measured, on the build at the bump's pin (066d6537) with the transpiled output
byte-identical to what the build produced - only the runtime patched:
before 6 app(s), 6 failing (all six the same assert)
after 6 app(s), 0 failing (four of them including their interaction)
Independently confirmed by the mechanism itself: rewriting the single
`#mt_names` private field to an ordinary property in the transpiled class, with
the runtime untouched, turns the same six green.
The e2e-debugging skill's "never PRIVATE" entry described the symptom as a law
of the runtime. It is a property of the corpus and of whether the shim ran, and
it now says so - with the question to ask first when a fresh assert names
nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JH8SXJBpjEgfZZnn2PzKYW
oblomov-dev
pushed a commit
that referenced
this pull request
Sep 8, 2026
Of the fourteen ports red at the new pin, six were the serializer (#185) and these five are one cause with two faces. None of them is a broken port. THE FRONTEND DROPS AN EVENT FIRED WHILE A ROUNDTRIP IS IN FLIGHT, silently: View1.eB returns early on `AppState.state.isBusy`, shows the busy indicator and does nothing else. No error, no console line, no request. From a Playwright leg that looks exactly like a wire that was never attached - the press fires, the listener is there, the backend never hears about it. meta/interactions/README.md has described this since 2026-08-27, and quotes the condition correctly. The three legs written against it then waited on `window.z2ui5.isBusy` - which is not where that flag lives. `window.z2ui5` is the frontend's PUBLIC global bag (AppState's getGlobal/setGlobal); isBusy is an internal field of AppState.state. The read was `undefined`, `!undefined` is true, and the guard returned at once. The reasoning was right and the code beside it read the wrong object. So lib-e2e.mjs gets waitForIdle( ), reading the flag where it is - and waiting for SUSTAINED quiet, because isBusy dips to false between a response landing and the next event being queued. Measured: a one-shot check returned while isBusy was still true one frame later, and the press was dropped anyway. 575, 578, 579 and 584 press a row as soon as the master table renders. Rendering a FlexibleColumnLayout fires columnResize, which all four wire to the backend - so the boot roundtrip they start was still open when the press landed. The failure read "pressing a row never opened the mid column", which is true and says nothing. Traced by asking the app: on_event( ) ran and get_event( ) answered COLUMN_RESIZE, so the LIST_ITEM press had never travelled at all. 101 had two faults of its own, both of them a locator that matched nothing while its target sat on screen: - the MessageBox's buttons carry UI5's own labels, "Yes" and "No" (__mbox-btn-0/1), not the raw action names the backend queues. `{ name: 'NO', exact: true }` is case-SENSITIVE, so it matched nothing. - a sap.m.Link with no href renders a bare <a>, and an <a> without href has no link role - so getByRole('link') resolved to nothing while the element was 27x17px, visible and hit-testable throughout. Both spent the full 30s actionability timeout and reported "locator.click: Timeout 30000ms exceeded". Playwright's call log named the cause both times ("waiting for getByRole(...)"); the leg was throwing that line away, so the message it printed was the one thing it knew that was useless. 109 and 558 carried the same hollow isBusy guard and were green with it. They are green with a real one too - which is the only reason to believe the guard had been doing nothing rather than something. Measured: 7/7 pass with their interactions, at the pin #181 proposes (6c433149) and with #185's shim. GATES PASS, 32/32. Still red, and each its own defect: 307, 350, 534. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JH8SXJBpjEgfZZnn2PzKYW
oblomov-dev
added a commit
that referenced
this pull request
Sep 8, 2026
…he framework (#186) * Five ports were losing their press to a guard that guarded nothing Of the fourteen ports red at the new pin, six were the serializer (#185) and these five are one cause with two faces. None of them is a broken port. THE FRONTEND DROPS AN EVENT FIRED WHILE A ROUNDTRIP IS IN FLIGHT, silently: View1.eB returns early on `AppState.state.isBusy`, shows the busy indicator and does nothing else. No error, no console line, no request. From a Playwright leg that looks exactly like a wire that was never attached - the press fires, the listener is there, the backend never hears about it. meta/interactions/README.md has described this since 2026-08-27, and quotes the condition correctly. The three legs written against it then waited on `window.z2ui5.isBusy` - which is not where that flag lives. `window.z2ui5` is the frontend's PUBLIC global bag (AppState's getGlobal/setGlobal); isBusy is an internal field of AppState.state. The read was `undefined`, `!undefined` is true, and the guard returned at once. The reasoning was right and the code beside it read the wrong object. So lib-e2e.mjs gets waitForIdle( ), reading the flag where it is - and waiting for SUSTAINED quiet, because isBusy dips to false between a response landing and the next event being queued. Measured: a one-shot check returned while isBusy was still true one frame later, and the press was dropped anyway. 575, 578, 579 and 584 press a row as soon as the master table renders. Rendering a FlexibleColumnLayout fires columnResize, which all four wire to the backend - so the boot roundtrip they start was still open when the press landed. The failure read "pressing a row never opened the mid column", which is true and says nothing. Traced by asking the app: on_event( ) ran and get_event( ) answered COLUMN_RESIZE, so the LIST_ITEM press had never travelled at all. 101 had two faults of its own, both of them a locator that matched nothing while its target sat on screen: - the MessageBox's buttons carry UI5's own labels, "Yes" and "No" (__mbox-btn-0/1), not the raw action names the backend queues. `{ name: 'NO', exact: true }` is case-SENSITIVE, so it matched nothing. - a sap.m.Link with no href renders a bare <a>, and an <a> without href has no link role - so getByRole('link') resolved to nothing while the element was 27x17px, visible and hit-testable throughout. Both spent the full 30s actionability timeout and reported "locator.click: Timeout 30000ms exceeded". Playwright's call log named the cause both times ("waiting for getByRole(...)"); the leg was throwing that line away, so the message it printed was the one thing it knew that was useless. 109 and 558 carried the same hollow isBusy guard and were green with it. They are green with a real one too - which is the only reason to believe the guard had been doing nothing rather than something. Measured: 7/7 pass with their interactions, at the pin #181 proposes (6c433149) and with #185's shim. GATES PASS, 32/32. Still red, and each its own defect: 307, 350, 534. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JH8SXJBpjEgfZZnn2PzKYW * The Calendar's second day has to be in the month the calendar is showing App 307 picked cell 0, then cell 1, and asserted two highlighted days. A sap.ui.unified.Calendar renders ONE month at a time - the 42-cell six-week grid around it - and the response to the first pick re-renders it around the day just selected, so the DOM order moves under the leg. Measured: cell 0 selected 2026-08-30, the grid came back on August, cell 1 was then 2026-07-27, and picking it re-rendered the grid on JULY, where 08-30 does not exist. Two dates in the model, two rows in the List, one highlighted cell, for ever - and the leg failed on the assertion that counts the highlighting. The neighbour is now found in the CURRENT grid, next to the cell that is actually selected and inside the same month (data-sap-day), with a wait for the frontend to go idle before it - the first pick is a roundtrip, and eB drops a press fired into one. Also records in the e2e-debugging skill what 350 and 534 turned out to be: not port defects but a frontend one - a follow_up_action( ) queued on the app's first response was dropped when the app's own render dispatched a second request - with the one measurement that tells the two apart. npm run gates and npm test (32 pass, 0 fail) are green; e2e-smoke --strict over 101, 109, 307, 350, 534, 558, 575, 578, 579, 584 reports 10 app(s), 0 failing against a build carrying that frontend fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JH8SXJBpjEgfZZnn2PzKYW --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Six ports have been 500ing on their first roundtrip — 049, 121, 241, 291, 299, 308 — with
and nothing naming a class or an attribute. The nightly has reported them for over a week (#162, #172, #173), and the weekly pin bump then read as "the framework broke six ports" (#181). It did not. This build was not running a shim the framework has been shipping since abap2UI5#2707.
What is actually happening
The draft is written with
CALL TRANSFORMATION id, whose transpiled re-implementation walks a serializable object attribute by attribute through a dynamicASSIGN obj->( name ). A PRIVATE ABAP attribute is a JavaScript#fieldin the transpiled class, which no name lookup reaches —sy-subrcis 4 and the writer'sASSERTdies. A real system serializes private attributes perfectly well, so nothing else can catch it: not ABAP, not abaplint, not the transpiler.Asking the kernel which attribute it could not reach:
All six ports bind with
omit_initial_paths, which makes the client hand inlcl_initial_paths_filter— a framework class, serializable by design, whosemt_namesis private. Nothing in the corpus was wrong.abap2UI5 knows this:
node/setup/patch-abaplint-runtime-assign.mjs, block 2, falls back to the transpiler'sFRIENDS_ACCESS_INSTANCE, and the framework runs that script before every one of its own transpiled runs (auto_transpile,unit,express).e2e-buildalready runs its downport sibling and simply never ran this one, so the backend served here had the unpatched runtime.The change
It runs it now, next to the other, and guarded: a pin older than #2707 has no such script and the build says so instead of failing.
Measured
On the build at the bump's pin (
066d6537), with the transpiled output byte-identical to what the build produced — only the runtime patched:6 app(s), 6 failing— all six the same assert6 app(s), 0 failing— four of them including their interactionIndependently confirmed by the mechanism itself: rewriting the single
#mt_namesprivate field to an ordinary property in the transpiled class, with the runtime untouched, turns the same six green.Two lines of enquiry were checked and ruled out: the transpiler bump (2.13.59 → 2.13.64) produces byte-identical output for the whole framework tree, and the runtime differs only by a new
skipstatement —assign.jsis identical.Gates green, 32/32 tests.
Not in this PR
The
e2e-debuggingskill's "never PRIVATE" entry described the symptom as a law of the runtime. It is a property of the corpus and of whether the shim ran, and it now says so — with the question to ask first when a fresh assert names nothing.#181 stays red on its own terms: its pin (
066d6537) predates #2707, so the shim does not exist there and the guard says so. The three other failures in it (101, 359 click timeouts, 307 "second day") are a separate matter; 101 and 307 are in today's nightly too, so they are not the bump's either.🤖 Generated with Claude Code
https://claude.ai/code/session_01JH8SXJBpjEgfZZnn2PzKYW
Generated by Claude Code