Skip to content

What the site claims about the framework, checked against the framework — plus a sixth gate so it stays checked - #176

Merged
oblomov-dev merged 9 commits into
mainfrom
claude/kleine-verbesserungen-fixes-ovyryc
Aug 21, 2026
Merged

What the site claims about the framework, checked against the framework — plus a sixth gate so it stays checked#176
oblomov-dev merged 9 commits into
mainfrom
claude/kleine-verbesserungen-fixes-ovyryc

Conversation

@oblomov-dev

Copy link
Copy Markdown
Member

Eight commits, all from one method: read each page against z2ui5_if_client at the release this site names, and against the framework source, rather than against itself.

The corrections

abap_bool does not travel as "X". The data-type table said it arrives as the JSON string "X" / "", and the Boolean section built two workarounds on that: an expression binding {= ${/MV_FLAG} === 'X' }, and a parallel string attribute translated before and after every event. AJSON maps the boolean ABAP types to a JSON boolean and boolc( )s them back; z2ui5_cl_ui5_srv_model's own tests assert "VALIDATED":true on the wire, and not one sample in abap2UI5/samples compares a bound value to 'X' — they bind abap_bool straight onto selected. The first workaround can never tick a box; the second is boilerplate for nothing. Rewritten to "bind it directly", keeping the thing that does bite: the mapping is keyed on the type, so a flag declared c LENGTH 1 travels as a string.

The app state does not travel to the browser, and it is not only the public half. Six places said the controller is "serialized to the client and back" and that only PUBLIC SECTION attributes survive. db_save hands all_xml_stringify( ) to z2ui5_cl_ui5_srv_draft=>create( ), which does MODIFY z2ui5_t_01: the state is a row in the database and never leaves the server; the browser carries a draft id and gets a new one every roundtrip. And serialization is CALL TRANSFORMATION id over the whole instance — PUBLIC is what _bind( ) needs to reach an attribute, not what keeps it alive (z2ui5_cl_smp_app_009 keeps its whole employee table protected across events). The advice that followed from the wrong version pushed working state into the public section, which is the opposite of what the framework's own guide says.

The binding error is not silent. Four pages said a PROTECTED/PRIVATE attribute passed to _bind( ) fails silently with "no compile-time or runtime error", and sent the reader to the browser console. main_attri_search raises BINDING_ERROR - No class attribute for binding found …, nothing catches it, and it lands in the error view.

The Security page credited a CSP directive the framework deliberately omits. The quoted default policy ended frame-ancestors 'self'; the framework stops two directives earlier and says why at the line where it is not — browsers ignore frame-ancestors in a <meta> CSP, so framing is blocked by the X-Frame-Options header instead. The page named the wrong mechanism and handed the reader that directive twice more to copy. Also: t_security_header was the one cs_config field no page mentioned, so the seven response headers the framework sets now have a section.

API that 1.143.0 removed, still being taught on five pages: client->set_nav_routing( ) (a deleted method) in routing.md and navigation.md, cs_event-nav_to_route and cs_event-history_back in the cs_event block and the browser-history section, view_model_update( ) recommended as a fix in nested_views.md, _event_client( ) and the Z2UI5_CL_HTTP_HANDLER shim in logout.md and authorization.md.

Smaller ones: control_global has seven whitelisted objects, the reference table listed four (POPUP, INVISIBLE_MESSAGE and FORMATTING were missing — understating a whitelist tells the reader something is impossible); p/decfloat* and timestamp/timestampl are JSON numbers, not strings; the User Exits page linked Z2UI5_IF_EXIT at a path it left when it was retired to src/99, so the link 404'd; four references to Z2UI5_CL_DEMO_APP_* classes that exist under no prefix; the size-limit page presented all five view slots as independent when MAIN/NEST/NEST2 share one model and the largest limit wins; and the formatter module the framework ships (z2ui5/model/formatter — the four helpers 26 samples use) was documented nowhere, while the formatter page told the reader three times to write a custom JS formatter.

The gate

Finding those took reading every page against the interface at the release tag. check:examples is the check that should have caught them and cannot: it compiles the fenced blocks that are whole classes, and almost every one of these lived somewhere it does not reach — a sentence, a two-line snippet, a constant block reproduced for reference. That is most of what a reader copies.

check:api-names asks four questions of every page, against z2ui5_if_client fetched at the release this site names:

client->NAME(              is NAME a method?
NAME = inside that call    is it one of its parameters?   (fenced ABAP only)
cs_GROUP-MEMBER            is MEMBER in that constant group?
blob/main/… link           does it resolve?

1543 names and 8 source links across 137 pages, in about a second. Truth is the release, never main — a page is correct when it matches what the reader can install; A2UI5_REF points it at main for a canary run. Unreachable network skips rather than fails, like check:samples. Pages whose subject is the removed names (deprecations, changelog, the action page) are exempt, or the gate would forbid documenting a migration.

Wired into npm run check, check.yml and deploy.yml in the same position; the gate table in AGENTS.md goes from five to six.

How to test

npm run check. All six gates green on this branch, including against the ten-step walkthrough and the new home page that landed on main in the meantime.


Generated by Claude Code

oblomov-dev and others added 9 commits August 21, 2026 10:24
Two small corrections, both about a page teaching an object the framework has
moved on from.

`configuration/authorization.md` called `z2ui5_cl_http_handler=>run( )`. That
class is the deprecated shim - an empty subclass of
`z2ui5_cl_ui5_http_handler`, which is what `get_started/quickstart.md` on this
same site already shows and what ships in 1.143.0, the release this site names.
Both examples on the page follow quickstart now.

The built-in popups are on the deprecation list (`resources/deprecations`, and
the popups add-on since 1.142.0), and `cookbook/expert_more/value_help.md`
already says so where it uses `Z2UI5_CL_POP_TO_SELECT`. Five other pages used
one without a word: exception (`POP_ERROR`), message and logging
(`POP_MESSAGES`), pdf (`POP_PDF`) and popup (`POP_TO_CONFIRM`). Each gets the
same warning value_help carries - the example stays as it is, because that is
what existing code contains, and the reader is told where new code should get
its popup from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
Five pages still described the framework as it was before the release this
site names. Everything below was checked against `z2ui5_if_client` at tag
1.143.0 and against a samples checkout.

Removed API, still being taught:

- `routing.md` opened with `client->set_nav_routing( )` — a METHOD deleted in
  1.143.0. Routing is switched on with
  `follow_up_action( cs_event-set_nav_routing )` now, exactly as samples 468
  and 480 do it. The "Navigating by Route" section built on
  `_event_client( cs_event-nav_to_route )`; that constant is gone too, and the
  navigation it wrapped is `nav_app_call( )`, which pushes the same route
  entry.
- `frontend.md` listed `nav_to_route` and `history_back` in the `cs_event`
  block. Neither exists in 1.143.0; both are dropped, and the sentence under
  the block points at `set_nav_routing` instead.
- `url_handling.md` announced "two client methods" for browser history and
  showed one — `history_back` was the other. It now says one, and shows the
  raw `history.back()` through `follow_up_action( )` that replaced it.
- `nested_views.md` told the reader to call `view_model_update( )` when a
  nested view looks stale. That method has done nothing since 1.143.0; the
  push is automatic, which the deprecations page already says and this page
  contradicted.
- `logout.md` used `_event_client( )`, obsolete since 1.143.0, and named
  `Z2UI5_CL_HTTP_HANDLER` — the empty shim — as the ICF handler class.

Samples that do not exist:

`Z2UI5_CL_DEMO_APP_139`, `_124` and `_085` (twice) come from a naming scheme
the samples repository left behind; no class of that name exists under any
prefix. Two are replaced by the real sample for the same thing
(`Z2UI5_CL_SMP_APP_322` for push state, `Z2UI5_CL_SMP_APP_098` for the FCL),
and the barcode pointer is dropped — the catalogue has no barcode sample to
point at. Every remaining `Z2UI5_CL_SMP_APP_*` on the site (98 of them) was
verified against the checkout.

`authorization.md` and the popup warnings from the previous commit are
unaffected; check:examples, check:samples, the anchor sweep and the VitePress
build are green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
… names

The last commit fixed four pages that taught API 1.143.0 had deleted -
`set_nav_routing( )`, `cs_event-nav_to_route`, `cs_event-history_back`,
`view_model_update( )`. Finding them took reading every page against the
interface at the release tag. Nothing here could have told anybody.

`check:examples` is the check that should have, and cannot: it compiles the
fenced blocks that are WHOLE CLASSES, and every one of those four lived
somewhere it does not reach - a sentence in the prose, a two-line snippet, the
`cs_event` block a page reproduces for reference. That is most of what a
reader copies.

So `check:api-names` asks three questions of every page, against
`z2ui5_if_client` fetched at the release this site names (never main - a page
is correct when it matches what the reader can install):

  `client->NAME(`            is NAME a method?
  `NAME = ` inside that call is it one of its parameters?   (fenced ABAP only)
  `cs_GROUP-MEMBER`          is MEMBER in that constant group?

1552 names on 137 pages, in about a second. `A2UI5_REF` points it at main for
a canary run. Unreachable network SKIPS rather than fails, like check:counts
and check:samples - a documentation gate must not go red because github.com is
down, and must not claim to have verified what it did not.

Pages whose SUBJECT is the removed names are exempt: deprecations, changelog
and the action page exist to spell the old spelling, and a gate that forbids
that forbids documenting a migration.

It found one more page in passing: `navigation.md` named
`client->set_nav_routing( )` in a sentence - the fifth site of the same
removal, and the one the last commit missed.

Wired into `npm run check`, check.yml and deploy.yml in the same position;
AGENTS.md's gate table (five -> six) follows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
The User Exits page links `Z2UI5_IF_EXIT` at
`abap2UI5/blob/main/src/02/z2ui5_if_exit.intf.abap`. The interface was retired
to the frozen `src/99` package when it was superseded by
`z2ui5_if_ui5_exit` - it still ships and is still called, it simply is not
there any more. Every reader who clicked that link got a 404, on the page whose
whole subject is implementing that interface.

The link now points where the file is, the tip about the rename says why the
path looks frozen, and it links the successor too.

`check:api-names` gains the same question for the rest of them: a
`github.com/abap2UI5/abap2UI5/blob/main/` link has to resolve. Eight distinct
URLs on the site, one HEAD each, and unreachable network skips that question
alone rather than the whole gate. Those links say `main`, so `main` is what
they are checked against - unlike the three name questions, which are pinned to
the release the site names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
…omits

The default policy quoted on that page ends
`object-src 'none'; base-uri 'self'; frame-ancestors 'self';`. The framework's
actual default ends two directives earlier - there is no `frame-ancestors`, and
`z2ui5_cl_ui5_user_exit` says why at the line where it is not:

  " NO frame-ancestors here: browsers IGNORE that directive in a <meta> CSP
  " (and log a console warning) - cross-origin framing is forbidden by the
  " real X-Frame-Options: SAMEORIGIN response header set below instead

So the page named the wrong mechanism for clickjacking protection, on the page
a reader consults precisely to find out what protects them - and handed them
that directive twice more to copy: once in the prose list of hardening
directives, once in the "drop 'unsafe-eval'" policy the page tells them to
paste into their own exit. Pasted, it does nothing but log a warning.

All three corrected against the source, and the reason recorded where the
question comes up.

The mechanism that DOES block framing was undocumented: `t_security_header` is
the one `cs_config` field no page mentioned. abap2UI5 sets seven headers on
every response - GET and POST alike, `set_response( )` runs after the dispatch
in `_main( )` - and they are configurable through the same user exit as the
CSP. New "Response headers" section, one row each, with what to do when a
proxy already sets one.

Everything else the page and its neighbours claim about framework defaults was
re-checked and holds: the theme fallback (`sap_horizon`), the bootstrap URL,
the 4-hour draft expiry, CSRF on by default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
`frontend.md` prints the reference table for the control-call events. Its
`control_global` row names `MESSAGE_TOAST`, `MESSAGE_BOX`, `BUSY_INDICATOR`
and `THEMING`. The whitelist in `core/actions/ControlCall.js` - and the ABAP
Doc on `follow_up_action( )` in 1.143.0, which lists all seven - also carries
`POPUP`, `INVISIBLE_MESSAGE` and `FORMATTING`.

Understating a whitelist is quieter than overstating one and costs more: a
reader looking for how to announce something to a screen reader, or how to
register a currency the standard type does not know, finds a table that says
those are not possible. So the three are added with what they are for and the
UI5 floor each needs (1.89 / 1.78 / 1.120), and the `set…` vs `add…`
distinction for currencies, which is silent when you pick the wrong one.

`VIEW_SLOTS` and `ROUTER` stay out - they are registered in the same map but
are framework internals, and the interface does not offer them either.

Also: "demo app 470" now names the class, `Z2UI5_CL_SMP_APP_470`, like every
other sample reference on the site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
…orkaround

for a problem that does not exist

All of this came out of reading the model pages against the framework rather
than against themselves.

**`abap_bool` does not travel as `"X"`.** The data-type table says it arrives
as the JSON string `"X"` / `""` and that "UI5's CheckBox expects true/false,
not X" - and the Boolean section of the formatter page builds two workarounds
on that: an expression binding `{= ${/MV_FLAG} === 'X' }`, and a parallel
`string` attribute translated before and after every event.

AJSON maps the boolean ABAP types to a JSON boolean and `boolc( )`s them back
on the way in; `z2ui5_cl_ui5_srv_model`'s own tests assert `"VALIDATED":true`
on the wire, and not one sample in abap2UI5/samples compares a bound value to
`'X'` - they bind `abap_bool` straight onto `selected`. So the first workaround
produces a checkbox that can never tick (it compares against a value that is
not there, and an expression binding cannot write back anyway), and the second
is boilerplate for nothing.

Rewritten to say: bind it directly. With the one thing that genuinely bites
kept - the mapping is keyed on the TYPE, so a flag declared `c LENGTH 1`
travels as a string and stays unchecked - and a pointer to the separate
question of a boolean written into the view as an attribute, where `abap_false`
really does render a control visible and `a( b = … )` is the answer.

**Two more rows of the same table.** `p LENGTH n DECIMALS m` / `decfloat*` are
JSON numbers, not "sent as a string to preserve precision", and `timestamp` /
`timestampl` are numbers too - the framework never turns on AJSON's
`format_datetime`, which is what would make them strings. `utclong` is the one
that is a string, so it gets its own row and a pointer at the formatter that
reads it.

**The binding error is not silent.** Three pages say a `PROTECTED`/`PRIVATE`
attribute passed to `_bind( )` fails silently and that there is "no
compile-time or runtime error", and send the reader to the browser console and
the network tab. `z2ui5_cl_ui5_srv_model=>main_attri_search` raises

  BINDING_ERROR - No class attribute for binding found -
  Please check if the bound values are public attributes of your class

nothing catches it on the way out, and it lands in the error view. Corrected on
all three, with the "where to look" turned from a hunt into reading the message.

Also on this pass:

- The size-limit page presents all five view slots as independent. MAIN, NEST
  and NEST2 share one JSON model through UI5 propagation, so the largest limit
  of the three wins and resetting one changes nothing while another asks for
  more. Only popup and popover are their own.
- The formatter module the framework ships (`z2ui5/model/formatter` - the four
  helpers 26 samples use for `dateValue` and inline icons) was documented
  nowhere, while the formatter page told the reader three times to write a
  custom JS formatter. It has a section now, including why an empty date must
  become `null` and not an `Invalid Date`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
…public half

Six places tell the same story about how state survives a roundtrip, and it is
wrong in both halves.

**Where it goes.** "abap2UI5 serializes the controller TO THE CLIENT and
deserializes it on the next request" (life_cycle), "Browser sends the
serialized app state to the backend" (statefulness), "the controller is
serialized to the client and back" (cheat sheet, common_failures).
`z2ui5_cl_ui5_app_cont=>db_save` hands `all_xml_stringify( )` to
`z2ui5_cl_ui5_srv_draft=>create( )`, which does `MODIFY z2ui5_t_01`. The state
is written to a row in the database and never leaves the server; the browser
carries a draft id, and gets a NEW one on every roundtrip. The one page that
had it right all along is `technical/how_it_all_works` §16, which the others
contradicted.

That is not a detail. A reader who believes their app state crosses the wire
draws conclusions about payload size and about what is exposed to the client,
and both are wrong.

**What survives.** "Only `PUBLIC SECTION` attributes of serializable types
survive", "the framework serializes public attributes after every roundtrip",
"anything stored in public attributes survives". Serialization is
`CALL TRANSFORMATION id` over the whole instance - visibility does not enter
into it. `PUBLIC` is what `_bind( )` needs in order to REACH an attribute, and
nothing else. `z2ui5_cl_smp_app_009` in abap2UI5/samples proves it: it keeps
its whole employee table in the `PROTECTED SECTION`, fills it on init and reads
it back in a later event, and only the filtered copy it binds is public.

The advice that follows from the wrong version is bad advice - it pushes
working state into the public section, where the framework then scans it on
every roundtrip for bindings that do not exist. The framework's own guide says
the opposite ("bound data goes in PUBLIC SECTION, everything else in
PROTECTED"), so the site was arguing with it.

Corrected on all six, plus:

- the walkthrough's step 4, where a beginner meets this first, and where the
  same "silently ignores private or protected ones" from the last commit had
  survived
- "Two-way" -> "both directions", the wording the ecosystem retired
- common_failures gains the two things a reader in that situation needs: that
  a serialization failure is reported (`APP_SERIALIZATION_ERROR`), and that
  the draft expires after four hours
- `how_it_all_works` §16 now names the table, `Z2UI5_T_01` - the diagrams
  still show the working name `z2ui5_t_draft`, which is worth saying once

Checked and found correct on this pass, so noted rather than touched:
`follow_up_action( )`'s raw-JS discrimination rule (the page's
`A-Z a-z 0-9 _` matches the `CO` test in the source character for character),
the view-definition page's account of what the builder does and does not
enforce, and the `check_on_event` once-per-roundtrip section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJBaqq5cFVUVMGr1BQvSVC
…erungen-fixes-ovyryc

# Conflicts:
#	docs/get_started/full_example.md
Copilot AI lite review requested due to automatic review settings August 21, 2026 14:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@oblomov-dev
oblomov-dev merged commit dc7c799 into main Aug 21, 2026
1 check passed
@oblomov-dev
oblomov-dev deleted the claude/kleine-verbesserungen-fixes-ovyryc branch August 21, 2026 14:55
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.

3 participants