From 67460c8b108d6f8138c3ef484746ed0b2a87c281 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 18:55:27 +0000 Subject: [PATCH 1/4] Hide the result strip of the AI settings once they are saved Z2UI5_CL_SMPS_APP_013 left the result strip of an earlier connection test or of a failed save on screen after a successful save, so a red "save failed" or a test answer for the old configuration stood next to the "Settings saved" toast. A successful save now clears it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Te3BRpRpxNhZAYnrQL6Gdt --- src/10/z2ui5_cl_smps_app_013.clas.abap | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/10/z2ui5_cl_smps_app_013.clas.abap b/src/10/z2ui5_cl_smps_app_013.clas.abap index 2f7ff5c..cf718e2 100644 --- a/src/10/z2ui5_cl_smps_app_013.clas.abap +++ b/src/10/z2ui5_cl_smps_app_013.clas.abap @@ -97,6 +97,11 @@ CLASS z2ui5_cl_smps_app_013 IMPLEMENTATION. TRY. z2ui5_cl_smps_llm_factory=>config_save( config_from_form( ) ). model_init( ). + " the strip reported an earlier test or a failed save - neither is + " true of the configuration that was just saved + CLEAR result_text. + result_type = `Information`. + result_visible = abap_false. client->message_toast_display( `Settings saved` ). CATCH z2ui5_cx_smps_llm INTO DATA(error). From 07257b67c2e4a7e313a11fc5f5ed492294e37ebf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 18:55:28 +0000 Subject: [PATCH 2/4] Map the SmartMultiInput's Not* conditions onto an excluding range Z2UI5_CL_SMPS_APP_319 translated every operation it did not list into an EQ on the first value, so NotEQ, NotBT, NotStartsWith, NotEmpty and the other negated conditions selected exactly the rows the user had asked to leave out. A Not* operation is now its positive twin with the sign turned round (NotContains included, which no longer ignores the exclude flag), Initial is read like Empty, and an operation the sample still does not know is left out of the range and named in the hint instead of guessed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Te3BRpRpxNhZAYnrQL6Gdt --- src/02/z2ui5_cl_smps_app_319.clas.abap | 50 ++++++++++++++++++-------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/02/z2ui5_cl_smps_app_319.clas.abap b/src/02/z2ui5_cl_smps_app_319.clas.abap index cd29198..865765f 100644 --- a/src/02/z2ui5_cl_smps_app_319.clas.abap +++ b/src/02/z2ui5_cl_smps_app_319.clas.abap @@ -267,6 +267,7 @@ CLASS z2ui5_cl_smps_app_319 IMPLEMENTATION. DATA option TYPE c LENGTH 2. DATA low TYPE string. DATA high TYPE string. + DATA skipped TYPE i. t_selopt = VALUE #( ). lr_type = VALUE #( ). @@ -277,21 +278,37 @@ CLASS z2ui5_cl_smps_app_319 IMPLEMENTATION. low = s-value1. high = s-value2. - CASE to_upper( s-operation ). - WHEN `EQ`. option = `EQ`. - WHEN `NE`. option = `NE`. - WHEN `CONTAINS`. option = `CP`. low = |*{ s-value1 }*|. - WHEN `NOTCONTAINS`. option = `CP`. low = |*{ s-value1 }*|. sign = `E`. - WHEN `STARTSWITH`. option = `CP`. low = |{ s-value1 }*|. - WHEN `ENDSWITH`. option = `CP`. low = |*{ s-value1 }|. - WHEN `BT`. option = `BT`. - WHEN `NB`. option = `NB`. - WHEN `GT`. option = `GT`. - WHEN `GE`. option = `GE`. - WHEN `LT`. option = `LT`. - WHEN `LE`. option = `LE`. - WHEN `EMPTY`. option = `EQ`. low = ``. - WHEN OTHERS. option = `EQ`. + " the Not* operations (NotEQ, NotBT, NotContains, NotEmpty, ...) are + " their positive twin with the sign turned round - read as EQ, they + " selected exactly the rows the user had asked to leave out + DATA(operation) = to_upper( s-operation ). + IF strlen( operation ) > 3 AND substring( val = operation len = 3 ) = `NOT`. + operation = substring( val = operation off = 3 ). + sign = COND #( WHEN sign = `I` THEN `E` ELSE `I` ). + ENDIF. + + CASE operation. + WHEN `EQ` OR ``. option = `EQ`. + WHEN `NE`. option = `NE`. + WHEN `CONTAINS`. option = `CP`. low = |*{ s-value1 }*|. + WHEN `STARTSWITH`. option = `CP`. low = |{ s-value1 }*|. + WHEN `ENDSWITH`. option = `CP`. low = |*{ s-value1 }|. + WHEN `BT`. option = `BT`. + WHEN `NB`. option = `NB`. + WHEN `GT`. option = `GT`. + WHEN `GE`. option = `GE`. + WHEN `LT`. option = `LT`. + WHEN `LE`. option = `LE`. + WHEN `EMPTY` OR `INITIAL`. + option = `EQ`. + low = ``. + high = ``. + WHEN OTHERS. + " an operation this mapping does not know is left out rather than + " guessed - an EQ on its first value would filter on something the + " user never asked for + skipped = skipped + 1. + CONTINUE. ENDCASE. APPEND VALUE #( sign = sign option = option low = low high = high ) TO lr_type. @@ -309,6 +326,9 @@ CLASS z2ui5_cl_smps_app_319 IMPLEMENTATION. ENDLOOP. hint = |{ lines( t_selopt ) } condition(s) -> { lines( t_result ) } of { lines( t_product ) } demo products match.|. ENDIF. + IF skipped > 0. + hint = |{ hint } { skipped } condition(s) with an operation this sample does not map were left out.|. + ENDIF. ENDMETHOD. From f3ca988a8aaf75972a5324df322b850a2f8ce6da Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 18:55:28 +0000 Subject: [PATCH 3/4] Keep sap-client in the overview's sample links inside the launchpad Both copies of url_param_get_tab (the overview app and z2ui5_cl_smps_context) kept only what followed sap-startup-params, so in the Fiori launchpad the sample links lost sap-client and sap-language and opened in the default client. They also decoded %26 and %3D in every value and cut at a `?` inside a value, dropping every parameter before it. Both now follow the fixed copy in abap2UI5's z2ui5_cl_ui5_util_context: cut at the first `?`, decode only the packed startup parameters, and keep what stands before them. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Te3BRpRpxNhZAYnrQL6Gdt --- src/00/00/z2ui5_cl_smps_context.clas.abap | 74 ++++++++++++++--------- src/z2ui5_cl_smps_app_000.clas.abap | 74 ++++++++++++++--------- 2 files changed, 94 insertions(+), 54 deletions(-) diff --git a/src/00/00/z2ui5_cl_smps_context.clas.abap b/src/00/00/z2ui5_cl_smps_context.clas.abap index b8ae903..84e5de8 100644 --- a/src/00/00/z2ui5_cl_smps_context.clas.abap +++ b/src/00/00/z2ui5_cl_smps_context.clas.abap @@ -1034,33 +1034,53 @@ CLASS z2ui5_cl_smps_context IMPLEMENTATION. METHOD url_param_get_tab. - DATA(lv_search) = replace( val = i_val - sub = `%3D` - with = `=` - occ = 0 ). - - " RFC 3986 allows lowercase hex digits in percent-encodings, so decode - " %3d the same way as %3D (%26 contains no letters and needs no twin) - lv_search = replace( val = lv_search - sub = `%3d` - with = `=` - occ = 0 ). - - lv_search = replace( val = lv_search - sub = `%26` - with = `&` - occ = 0 ). - - lv_search = shift_left( val = lv_search sub = `?` ). - - " prepend & before searching so sap-startup-params is also unwrapped - " when it is the first/only query parameter (typical FLP target mapping) - DATA(lv_search2) = substring_after( val = |&{ lv_search }| sub = `&sap-startup-params=` ). - lv_search = COND #( WHEN lv_search2 IS NOT INITIAL THEN lv_search2 ELSE lv_search ). - - lv_search2 = substring_after( val = lv_search sub = `?` ). - IF lv_search2 IS NOT INITIAL. - lv_search = lv_search2. + " a full URL or a request URI carries a path before its query: cut at + " the FIRST `?` only. A later `?` is a legal, unencoded character of a + " value (`title=why?`) - cutting there dropped every parameter before it + " declared, not inline: DATA( ) from a generic CLIKE parameter is "fixed + " type STRING used for generic type CLIKE" in the extended check + DATA lv_search TYPE string. + lv_search = i_val. + IF lv_search CS `?`. + lv_search = substring_after( val = lv_search sub = `?` ). + ENDIF. + + " The FLP packs the target's own parameters into ONE value, + " sap-startup-params, with its `=` and `&` percent-encoded. Prepend & + " before searching so it is also unwrapped when it is the first/only + " query parameter (typical FLP target mapping). Only THAT value is + " decoded: decoding the whole query first tore every legitimately encoded + " `&` or `=` in any other value apart (`a=x%26y` became a=x and y=), and + " what stands BEFORE the wrapper - sap-client, sap-language - is kept: + " it used to be dropped from every link built from this table + " (abap2UI5's z2ui5_cl_ui5_util_context carries the same fix) + DATA(lv_startup) = substring_after( val = |&{ lv_search }| sub = `&sap-startup-params=` ). + IF lv_startup IS NOT INITIAL. + " lv_before keeps the prepended `&`, so the empty first segment it + " produces below is skipped like a trailing one + DATA(lv_before) = substring_before( val = |&{ lv_search }| sub = `&sap-startup-params=` ). + SPLIT lv_startup AT `&` INTO DATA(lv_packed) DATA(lv_rest). + lv_packed = replace( val = lv_packed + sub = `%3D` + with = `=` + occ = 0 ). + " RFC 3986 allows lowercase hex digits in percent-encodings, so decode + " %3d the same way as %3D (%26 contains no letters and needs no twin) + lv_packed = replace( val = lv_packed + sub = `%3d` + with = `=` + occ = 0 ). + lv_packed = replace( val = lv_packed + sub = `%26` + with = `&` + occ = 0 ). + lv_search = lv_packed. + IF lv_rest IS NOT INITIAL. + lv_search = |{ lv_packed }&{ lv_rest }|. + ENDIF. + IF lv_before IS NOT INITIAL. + lv_search = |{ lv_before }&{ lv_search }|. + ENDIF. ENDIF. SPLIT lv_search AT `&` INTO TABLE DATA(lt_param). diff --git a/src/z2ui5_cl_smps_app_000.clas.abap b/src/z2ui5_cl_smps_app_000.clas.abap index fe84077..930dc78 100644 --- a/src/z2ui5_cl_smps_app_000.clas.abap +++ b/src/z2ui5_cl_smps_app_000.clas.abap @@ -1072,33 +1072,53 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. METHOD url_param_get_tab. - DATA(lv_search) = replace( val = val - sub = `%3D` - with = `=` - occ = 0 ). - - " RFC 3986 allows lowercase hex digits in percent-encodings, so decode - " %3d the same way as %3D (%26 contains no letters and needs no twin) - lv_search = replace( val = lv_search - sub = `%3d` - with = `=` - occ = 0 ). - - lv_search = replace( val = lv_search - sub = `%26` - with = `&` - occ = 0 ). - - lv_search = shift_left( val = lv_search sub = `?` ). - - " prepend & before searching so sap-startup-params is also unwrapped - " when it is the first/only query parameter (typical FLP target mapping) - DATA(lv_search2) = substring_after( val = |&{ lv_search }| sub = `&sap-startup-params=` ). - lv_search = COND #( WHEN lv_search2 IS NOT INITIAL THEN lv_search2 ELSE lv_search ). - - lv_search2 = substring_after( val = lv_search sub = `?` ). - IF lv_search2 IS NOT INITIAL. - lv_search = lv_search2. + " a full URL or a request URI carries a path before its query: cut at + " the FIRST `?` only. A later `?` is a legal, unencoded character of a + " value (`title=why?`) - cutting there dropped every parameter before it + " declared, not inline: DATA( ) from a generic CLIKE parameter is "fixed + " type STRING used for generic type CLIKE" in the extended check + DATA lv_search TYPE string. + lv_search = val. + IF lv_search CS `?`. + lv_search = substring_after( val = lv_search sub = `?` ). + ENDIF. + + " The FLP packs the target's own parameters into ONE value, + " sap-startup-params, with its `=` and `&` percent-encoded. Prepend & + " before searching so it is also unwrapped when it is the first/only + " query parameter (typical FLP target mapping). Only THAT value is + " decoded: decoding the whole query first tore every legitimately encoded + " `&` or `=` in any other value apart (`a=x%26y` became a=x and y=), and + " what stands BEFORE the wrapper - sap-client, sap-language - is kept: + " it used to be dropped from every link built from this table + " (abap2UI5's z2ui5_cl_ui5_util_context carries the same fix) + DATA(lv_startup) = substring_after( val = |&{ lv_search }| sub = `&sap-startup-params=` ). + IF lv_startup IS NOT INITIAL. + " lv_before keeps the prepended `&`, so the empty first segment it + " produces below is skipped like a trailing one + DATA(lv_before) = substring_before( val = |&{ lv_search }| sub = `&sap-startup-params=` ). + SPLIT lv_startup AT `&` INTO DATA(lv_packed) DATA(lv_rest). + lv_packed = replace( val = lv_packed + sub = `%3D` + with = `=` + occ = 0 ). + " RFC 3986 allows lowercase hex digits in percent-encodings, so decode + " %3d the same way as %3D (%26 contains no letters and needs no twin) + lv_packed = replace( val = lv_packed + sub = `%3d` + with = `=` + occ = 0 ). + lv_packed = replace( val = lv_packed + sub = `%26` + with = `&` + occ = 0 ). + lv_search = lv_packed. + IF lv_rest IS NOT INITIAL. + lv_search = |{ lv_packed }&{ lv_rest }|. + ENDIF. + IF lv_before IS NOT INITIAL. + lv_search = |{ lv_before }&{ lv_search }|. + ENDIF. ENDIF. SPLIT lv_search AT `&` INTO TABLE DATA(lt_param). From 8e702ffeba9fed22b899d221f900f4a938bd1ee9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 19:09:10 +0000 Subject: [PATCH 4/4] Show a posted news item as text in the other sessions of the AMC sample Z2UI5_CL_SMPS_APP_489 put the text that arrives over the channel straight into a FeedListItem, which renders its text as FormattedText - so markup one user typed (a link, bold, a list) reached every other open session as markup. The text is now HTML-escaped on the way into the feed, the way Z2UI5_CL_SMPS_APP_014 already does it for its chat. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Te3BRpRpxNhZAYnrQL6Gdt --- src/07/z2ui5_cl_smps_app_489.clas.abap | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/07/z2ui5_cl_smps_app_489.clas.abap b/src/07/z2ui5_cl_smps_app_489.clas.abap index 041e9e2..42ccddb 100644 --- a/src/07/z2ui5_cl_smps_app_489.clas.abap +++ b/src/07/z2ui5_cl_smps_app_489.clas.abap @@ -52,6 +52,14 @@ CLASS z2ui5_cl_smps_app_489 DEFINITION PUBLIC. RETURNING VALUE(result) TYPE string. + "! the three characters FormattedText reads as markup - escaped, so a + "! news item somebody else posted shows the text they typed + METHODS html_escape + IMPORTING + val TYPE string + RETURNING + VALUE(result) TYPE string. + ENDCLASS. @@ -148,9 +156,12 @@ CLASS z2ui5_cl_smps_app_489 IMPLEMENTATION. RETURN. ENDIF. + " a FeedListItem renders its text as FormattedText, and this text + " was typed by whoever is on the channel - escaped, so a posted + " or reaches the other sessions as the text it is INSERT VALUE ty_s_news( - text = json_get_string( json = ws_message - name = `text` ) + text = html_escape( json_get_string( json = ws_message + name = `text` ) ) author = json_get_string( json = ws_message name = `author` ) ) INTO TABLE t_news. @@ -194,6 +205,16 @@ CLASS z2ui5_cl_smps_app_489 IMPLEMENTATION. ENDMETHOD. + METHOD html_escape. + + " the ampersand FIRST, or the other two would be escaped twice + result = replace( val = val sub = `&` with = `&` occ = 0 ). + result = replace( val = result sub = `<` with = `<` occ = 0 ). + result = replace( val = result sub = `>` with = `>` occ = 0 ). + + ENDMETHOD. + + METHOD json_get_string. " Reads one string field of the flat object json_build( ) writes. Finding