Skip to content

The e2e backend runs the framework's runtime shim, like the framework does - #185

Merged
oblomov-dev merged 1 commit into
mainfrom
claude/documentation-navigation-error-g4kgod
Sep 8, 2026
Merged

The e2e backend runs the framework's runtime shim, like the framework does#185
oblomov-dev merged 1 commit into
mainfrom
claude/documentation-navigation-error-g4kgod

Conversation

@oblomov-dev

Copy link
Copy Markdown
Member

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.

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 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.

Asking the kernel which attribute it could not reach:

UNREACHABLE class=LCL_INITIAL_PATHS_FILTER attribute=MT_NAMES
   ATTRIBUTES=["MT_NAMES:I","Z2UI5_IF_AJSON_FILTER~VISIT_TYPE:U"]

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.

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:

result
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.

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 skip statement — assign.js is identical.

Gates green, 32/32 tests.

Not in this PR

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.

#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

… 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
oblomov-dev merged commit fa32d1e into main Sep 8, 2026
19 checks passed
@oblomov-dev
oblomov-dev deleted the claude/documentation-navigation-error-g4kgod branch September 8, 2026 12:14
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>
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.

2 participants