diff --git a/AGENTS.md b/AGENTS.md index 730a67a4..4e6a4290 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -823,7 +823,7 @@ serve it, and open each example in it, checking that the status line reaches `running` and that the app frame contains something: ```sh -npm run runnable -- --json > /tmp/runnable.json # 63 examples, ABAP included +npm run runnable -- --json > /tmp/runnable.json # 64 examples, ABAP included git clone https://github.com/abap2UI5/playground && cd playground npm ci && npm run build # the first build is a few minutes RUNNABLE_JSON=/tmp/runnable.json npm test -- docs-examples diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 84aef0a7..3e9aad87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,8 +10,8 @@ npm run docs:dev # the site at localhost, with hot reload, while writing npm run check # everything CI runs on a pull request ``` -`npm run check` is `check.yml`: the test suite, then the VitePress build, then -the gates that hold the pages to what the other repositories actually ship — +`npm run check` is `check.yml`: the test suite, then the site build (and the +VitePress build as a second opinion), then the gates that hold the pages to what the other repositories actually ship — the version the site names, the examples it prints, the Run buttons the playground can honour, the API names and the sample links. A page that quotes another repository is checked against that repository, not against memory, diff --git a/README.md b/README.md index 9b34a9cf..5fcf9082 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ npm run check # what CI runs, all fifteen steps A documentation repository has no compiler for its prose, but fifteen things in it are decidable, and `npm run check` decides all fifteen before a merge — the -prose builds (`docs:build`), the four bars are still made of the same palette, +site builds (`build`, the pages as they are published, and `docs:build`, the +VitePress second opinion), the four bars are still made of the same palette, type and radii as the playground's (`check:design`), every link into a neighbouring site on this origin — the playground, the catalogue — still leads there rather than to this site's @@ -30,13 +31,16 @@ own 404 (`check:cross-site`), the fenced ABAP examples compile and the views they build name real UI5 API (`check:examples`), those examples are written in the same house style as the sample corpora — chain layout and class shell — (`check:conventions`), every `client->` name and -`cs_*` constant the prose and snippets mention still exists in the release the -site names (`check:api-names`), the generated client API reference still -matches the interface at that release (`check:api-reference`), the sample +`cs_*` constant the prose and snippets mention still exists in the framework +on `main` (`check:api-names`), the generated client API reference still +matches the interface there (`check:api-reference`), the sample links still match the sample repositories (`check:samples`), every complete app example carries a playground Run button or its declared reason not to (`check:playground`), the release -number in the nav bar still matches the framework (`check:version`), and the +number in the nav bar still matches the framework (`check:version`), every +image is the format and the size a page can afford (`check:images`), a page +that is wrapped stays wrapped (`check:line-length`), every prose word is one +a dictionary or the project's own list knows (`check:vocabulary`), and the catalogue parser still parses (`test`). `.github/workflows/check.yml` runs the same list in the same order, so a green `npm run check` locally is a green pull request — and `deploy.yml` runs it @@ -53,7 +57,7 @@ changing anything beyond prose. The bar is the mark, then the four sections — **Home**, **Documentation**, **Samples**, **Playground** — then one search box, then the project's links. -The two middle sections are this site; Samples and Playground are the two +The first two sections are this site; Samples and Playground are the two neighbouring deployments on the same origin, and all four bars across them are kept identical by hand. @@ -83,7 +87,7 @@ than on the site. The client API is published for the same reader as one JSON document, [client-api.json](https://abap2ui5.github.io/docs/api/client-api.json) — -generated from `z2ui5_if_client` at the pinned release by +generated from `z2ui5_if_client` on the framework's `main` by `npm run generate:api`, committed, and held fresh by `check:api-reference`. The human-readable half is the [Client API page](https://abap2ui5.github.io/docs/resources/api.html). @@ -103,8 +107,9 @@ Which blocks get a button is decided in `docs/.vitepress/playground.mjs`, and the rule is narrow on purpose — a button on an example that cannot run is worse than no button. It has to be a complete class implementing `z2ui5_if_app` that displays something and needs nothing the browser has not got: no table of its -own, no CDS entity, no add-on repository, no on-premise SAP class. **38 of the -261 ABAP blocks here** clear that today. Every rule was written from an example +own, no CDS entity, no add-on repository, no on-premise SAP class. **64 of the +83 complete app classes here** clear that today (`npm run check:playground` +prints the current count). Every rule was written from an example watched failing in a real playground; `test/playground.test.mjs` keeps one fixture per shape, and [AGENTS.md](AGENTS.md) says how to redo the measurement. diff --git a/SECURITY.md b/SECURITY.md index 8b4780e0..b6eb91df 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,8 +19,8 @@ deployed by merging it. ## What this site is, from a security point of view -- **There is no server.** The site is static files built by VitePress and - served from GitHub Pages. No backend, no SAP system, no account, no session, +- **There is no server.** The site is static files, written by + `scripts/build-site.mjs` and served from GitHub Pages. No backend, no SAP system, no account, no session, and nothing a reader types is sent anywhere. - **The one thing that executes is the Run button.** A fenced example that carries one links into @@ -34,15 +34,19 @@ deployed by merging it. checked against, `abap2UI5` and its sibling repositories at a pinned release. A wrong statement here is usually wrong there first — but report it here if that is where you found it. -- **Dependencies are pinned**, and the generators fetch only from - `raw.githubusercontent.com` at that pin. +- **Dependencies are pinned.** What the build fetches beyond them is the + organisation's own: the framework's interface and the sample catalogues from + `raw.githubusercontent.com`, and the bar, two stylesheets, the search box and + the ABAP highlighter from the playground's published site + (`abap2ui5.github.io/playground`), which the build runs to colour the + examples - so a compromise of that deployment would reach this build. ## Out of scope - A page that is out of date, incomplete, or wrong about the framework. That is a documentation bug and belongs in - [an issue](https://github.com/abap2UI5/docs/issues) — it is what the eight - checks in [AGENTS.md](AGENTS.md) exist to catch, so a report that names one + [an issue](https://github.com/abap2UI5/docs/issues) — it is what the fifteen + gates in [AGENTS.md](AGENTS.md) exist to catch, so a report that names one they missed is genuinely useful. - Anything a reader does to their own browser tab with code they wrote themselves. diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index b2f1e435..96c4e145 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -50,7 +50,7 @@ export default defineConfig({ ["link", { rel: "shortcut icon", href: "/docs/favicon.ico" }], [ "link", - { rel: "apple-touch-icon", sizes: "180x180", href: "/docs/favicon.ico" }, + { rel: "apple-touch-icon", sizes: "180x180", href: "/docs/apple-touch-icon.png" }, ], // THE TYPE. One file, from this origin, and preloaded because it is what // the first line of every page is set in — a `@font-face` is only diff --git a/docs/.vitepress/playground.mjs b/docs/.vitepress/playground.mjs index 40003c92..a93e0007 100644 --- a/docs/.vitepress/playground.mjs +++ b/docs/.vitepress/playground.mjs @@ -72,7 +72,7 @@ const DECLARED_NAME = /^\s*(?:CLASS|INTERFACE)\s+([a-zA-Z_]\w*)\s+(?:DEFINITION| const MAX_NAME = 30; /** Something has to arrive on screen. */ -const DISPLAYS_SOMETHING = /->\s*(?:view|popup|message_box|message_toast|nest_view)[a-z_]*_display\s*\(/i; +const DISPLAYS_SOMETHING = /->\s*(?:view|popup|popover|message_box|message_toast|nest_view)[a-z_]*_display\s*\(/i; /* The database in the page holds the framework's own tables and what open-abap * ships. A business table is not among them, and `SELECT` from one is the @@ -206,7 +206,7 @@ function tablesUsed(code) { function unimplementedMethods(code) { const definition = code.split(CLASS_IMPLEMENTATION)[0]; const declared = []; - for (const m of definition.matchAll(/^\s*METHODS:?\s+([^.]+)\./gim)) { + for (const m of definition.matchAll(/^\s*(?:CLASS-)?METHODS:?\s+([^.]+)\./gim)) { for (const one of m[1].split(',')) { const name = /^\s*([a-z_]\w*)/i.exec(one)?.[1]; /* A redefinition or an interface method is implemented under another diff --git a/docs/.vitepress/theme/SiteNav.vue b/docs/.vitepress/theme/SiteNav.vue index 33df9ec4..16c2fc70 100644 --- a/docs/.vitepress/theme/SiteNav.vue +++ b/docs/.vitepress/theme/SiteNav.vue @@ -51,6 +51,10 @@ const onHome = computed(() => page.value.relativePath === "index.md"); * section. */ const samplesHref = ref(SAMPLES); const docsHref = ref(DOCS); +/* And the Playground's, which was bound in the markup and declared nowhere: + * the item rendered without an href, and leave( ) then sent it to the + * catalogue - the one branch it had for anything that was not the manual. */ +const playgroundHref = ref(PLAYGROUND); /* Documentation restores WHEREVER IN THE MANUAL you were, which is why it * passes a scope: the link is written at the manual's first page, and a stored @@ -65,6 +69,7 @@ const docsHref = ref(DOCS); const lift = () => { samplesHref.value = lastVisited("samples", SAMPLES); docsHref.value = lastVisited("docs", DOCS, HOME); + playgroundHref.value = lastVisited("playground", PLAYGROUND); }; /* The lift that cannot be missed: on the click itself, on the element, because * a ref set in the handler reaches the DOM a tick too late. @@ -85,11 +90,9 @@ const leave = (e) => { * in theme/index.js is throttled, and a click that lands inside its window * would otherwise store an offset from before the last scroll. */ rememberScroll(); - if (el.dataset.site) { - el.href = el.dataset.site === "docs" - ? lastVisited("docs", DOCS, HOME) - : lastVisited("samples", SAMPLES); - } + if (el.dataset.site === "docs") el.href = lastVisited("docs", DOCS, HOME); + else if (el.dataset.site === "samples") el.href = lastVisited("samples", SAMPLES); + else if (el.dataset.site === "playground") el.href = lastVisited("playground", PLAYGROUND); handOff(el.href); }; diff --git a/docs/.vitepress/theme/playground.js b/docs/.vitepress/theme/playground.js index 278036c5..761a152e 100644 --- a/docs/.vitepress/theme/playground.js +++ b/docs/.vitepress/theme/playground.js @@ -51,7 +51,13 @@ function loader() { window.abap2ui5Embed ? resolve(window.abap2ui5Embed) : reject(new Error('The playground loader did not install itself.'))); - script.addEventListener('error', () => reject(new Error(`${LOADER} could not be loaded.`))); + script.addEventListener('error', () => { + /* Forgotten, not kept: a load that failed once (offline for a moment) + * must not answer every later press on the page with the same error. */ + loading = undefined; + script.remove(); + reject(new Error(`${LOADER} could not be loaded.`)); + }); document.head.append(script); }); return loading; diff --git a/docs/.vitepress/theme/search-engine.js b/docs/.vitepress/theme/search-engine.js index f4725d19..951f3d34 100644 --- a/docs/.vitepress/theme/search-engine.js +++ b/docs/.vitepress/theme/search-engine.js @@ -342,7 +342,7 @@ export function grouped(hits, { perGroup = 8 } = {}) { return groups.map(({ label, hits: rows, total }) => ({ label, hits: rows, total })); } -/** The index, fetched once. Callers await this on the first keystroke, never +/** The index, fetched once. Callers await this when the box is opened, never * at load: a reader who does not search pays nothing for the box. */ let pending = null; export function loadIndex(url = INDEX_URL, { fetchFn = globalThis.fetch } = {}) { diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index ca062fed..9cdcff3b 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -2366,7 +2366,10 @@ .Layout a:focus-visible, .Layout button:focus-visible, .Layout summary:focus-visible, -.Layout input:focus-visible, +.Layout input:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} /* ---- a number beside every line, and an address for it ---- * diff --git a/docs/advanced/extensibility/frontend.md b/docs/advanced/extensibility/frontend.md index 1a54ee50..fa9162a3 100644 --- a/docs/advanced/extensibility/frontend.md +++ b/docs/advanced/extensibility/frontend.md @@ -11,7 +11,7 @@ Open VS Code (or your editor of choice), then run in the terminal: git clone https://github.com/abap2UI5/abap2UI5 cd abap2UI5/app ``` -Next replace `<>` in the three config files — `ui5.yaml`, `ui5-local.yaml` and `ui5-mock.yaml` — so they all point to your ABAP backend system: +Next replace `<>` in the two config files — `ui5.yaml` and `ui5-local.yaml` — so both point to your ABAP backend system: Frontend config files where the backend system URL must be replaced diff --git a/docs/advanced/extensibility/user_exits.md b/docs/advanced/extensibility/user_exits.md index 19ead5ce..96099f13 100644 --- a/docs/advanced/extensibility/user_exits.md +++ b/docs/advanced/extensibility/user_exits.md @@ -6,7 +6,7 @@ outline: [2, 4] abap2UI5 offers predefined user exits for tweaking the standard behavior. The interface [`Z2UI5_IF_UI5_EXIT`](https://github.com/abap2UI5/abap2UI5/blob/main/src/02/z2ui5_if_ui5_exit.intf.abap) exposes the user exits. To use them on your system, build a class that implements the interface and its methods. The abap2UI5 class [`Z2UI5_CL_UI5_USER_EXIT`](https://github.com/abap2UI5/abap2UI5/blob/main/src/01/04/z2ui5_cl_ui5_user_exit.clas.abap) calls them dynamically. Put your class in a custom package — **not** in the abap2UI5 packages. The interface exposes two exit methods: -- **`set_config_http_get`** — called on the initial HTTP GET request (page load). Use it to set frontend properties like the UI5 theme, the UI5 version, or the inline CSS. +- **`set_config_http_get`** — called on the initial HTTP GET request (page load), and again per response for its `t_security_header`, which every response carries. Use it to set frontend properties like the UI5 theme, the UI5 version, or the inline CSS. - **`set_config_http_post`** — called on every later HTTP POST request (each roundtrip). Use it to set backend behavior like the draft expiration time. Both methods take a `cs_config` changing parameter whose fields you can set as needed. The example below changes the theme and how long the backend keeps drafts: @@ -23,7 +23,7 @@ CLASS zcl_a2ui5_user_exit IMPLEMENTATION. METHOD z2ui5_if_ui5_exit~set_config_http_get. - cs_config-theme = `sap_belize`. + cs_config-theme = `sap_horizon_dark`. ENDMETHOD. diff --git a/docs/advanced/insights/12-where-your-own-javascript-goes.md b/docs/advanced/insights/12-where-your-own-javascript-goes.md index 074e0a8f..56008306 100644 --- a/docs/advanced/insights/12-where-your-own-javascript-goes.md +++ b/docs/advanced/insights/12-where-your-own-javascript-goes.md @@ -42,10 +42,12 @@ load. Write it as a backtick literal rather than a string template: a template has to escape every brace, and one missed escape is a parser error on the whole statement instead of a wrong string. Ask how we know. -**Everything else is a system decision, not an app decision.** Extra JavaScript -for the initial page is `custom_js` in the HTTP GET configuration, set in the -[user exit](/advanced/extensibility/user_exits) through `z2ui5_if_ui5_exit` — -one place, reviewable, and the same for every app in the system. +**Everything else is a system decision, not an app decision.** Frontend code +of your own is a custom control in the customer frontend BSP (`z2ui5_ccc`), +and what the initial page loads is the bootstrap the +[user exit](/advanced/extensibility/user_exits) sets through +`z2ui5_if_ui5_exit` — one place, reviewable, and the same for every app in +the system. None of them lets an app change the framework, and none requires the framework to change for an app. No plugin registry to learn, and no pull request to wait diff --git a/docs/advanced/insights/22-who-may-start-which-app.md b/docs/advanced/insights/22-who-may-start-which-app.md index a250de52..c10ce50a 100644 --- a/docs/advanced/insights/22-who-may-start-which-app.md +++ b/docs/advanced/insights/22-who-may-start-which-app.md @@ -52,7 +52,7 @@ with the refusal branch and what it should say, is on [Authorization](/configuration/authorization). What the framework itself brings to the table is the part a web application -needs and a report never did. A CSRF token on every POST, on by default. A +needs and a report never did. A CSRF check on every POST, on by default. A Content Security Policy on the first page, restricting what the browser will load, tightened in the user exit when an old UI5 release no longer has to be served. Error details off in production, as diff --git a/docs/advanced/renaming.md b/docs/advanced/renaming.md index 3f2e3c9c..3968ba84 100644 --- a/docs/advanced/renaming.md +++ b/docs/advanced/renaming.md @@ -67,7 +67,7 @@ alone would let them collide; the segment is what keeps them apart. | `smpc` | [samples-controls](https://github.com/abap2UI5/samples-controls) — control samples | | `popup` | [popups](https://github.com/abap2UI5-addons/popups) — the popups [add-on](/resources/addons) | | `cci` | [custom-controls](https://github.com/abap2UI5-addons/custom-controls) — custom controls shared with the community | -| `ccc` | [custom-controls-customer](https://github.com/abap2UI5/custom-controls-customer) — the template for **your own** frontend artifacts | +| `ccc` | [custom-controls-customer](https://github.com/abap2UI5-addons/custom-controls-customer) — the template for **your own** frontend artifacts | The two tables share one rule and one exception. The rule: an object's segment tells you which repository it was pulled from. The exception: frozen code keeps @@ -106,7 +106,7 @@ hierarchy in your system. For the framework repository that is: |---|---| | `src/00/` | External libraries — ajson, S-RTTI and the vendored context/HTTP helpers | | `src/01/` | Internal use only — draft persistence, request handling, event routing, binding, and the generated frontend | -| `src/02/` | The released API — the six objects above | +| `src/02/` | The released API — the five objects above | | `src/99/` | Frozen — see [Deprecations](/resources/deprecations) | The package a class sits in is the honest answer to "may I use this?": `src/02` @@ -191,13 +191,13 @@ instead of `z2ui5_if_app`. } ``` -The renamed copy is a complete, installable abapGit project under your own namespace — install it side by side with the original, pin it to a release, or ship it inside your product. The abap2UI5 CI runs this transformation on every change (`npm run rename`, workflow `test_rename.yaml`) to guarantee the codebase stays renameable. +The renamed copy is a complete, installable abapGit project under your own namespace — install it side by side with the original, pin it to a release, or ship it inside your product. The abap2UI5 CI runs this transformation on every change (`npm run rename`, workflow `build-rename.yaml`) to guarantee the codebase stays renameable. ### Step-by-Step Guide -Everything is already set up in the main repository: the on-demand GitHub Action `build_rename` renames all artifacts to a namespace of your choice and pushes the result as a ready-to-install branch. Renaming abap2UI5 takes just two steps: +Everything is already set up in the main repository: the on-demand GitHub Action `build-rename` renames all artifacts to a namespace of your choice and pushes the result as a ready-to-install branch. Renaming abap2UI5 takes just two steps: 1. **Fork** the [abap2UI5 repository](https://github.com/abap2UI5/abap2UI5) -2. **Run the Action** — in your fork, open the *Actions* tab (enable workflows when asked), select the **build_rename** workflow, and start it with your new namespace (a letter followed by letters, digits or underscores, max. 10 characters, e.g., `ZMYUI5`) +2. **Run the Action** — in your fork, open the *Actions* tab (enable workflows when asked), select the **build-rename** workflow, and start it with your new namespace (a letter followed by letters, digits or underscores, max. 10 characters, e.g., `ZMYUI5`) The workflow runs `abaplint --rename` with the checked-in configuration `.github/abaplint/rename.jsonc` and pushes the renamed sources to the branch `rename_` (e.g., `rename_zmyui5`). The branch contains the complete renamed `src` tree together with a matching `.abapgit.xml` — **pull it with abapGit** into your ABAP system for a parallel installation next to the original. @@ -220,9 +220,10 @@ in `CATCH cx_root`, so nothing is reported: the installation just runs with the default configuration and your [user exit](/advanced/extensibility/user_exits) is never called — no custom theme, no bootstrap configuration, no CSP override. -::: warning Patch the two literals after renaming +::: warning Patch the literals after renaming In your renamed branch, change the literals in `get_user_exit_class( )` to your -own namespace (`` `ZMYUI5_IF_EXIT` `` and `` `ZMYUI5_CL_UI5_USER_EXIT` ``). Only +own namespace (`` `ZMYUI5_IF_UI5_EXIT` ``, `` `ZMYUI5_CL_UI5_USER_EXIT` `` and +the `` `ZMYUI5_IF_EXIT` `` fallback beside them). Only apps that use a user exit are affected — everything else in the renamed installation works without a change. ::: diff --git a/docs/advanced/vscode.md b/docs/advanced/vscode.md index a8ebc5cb..6e9fb2da 100644 --- a/docs/advanced/vscode.md +++ b/docs/advanced/vscode.md @@ -321,7 +321,7 @@ navigation map; the Control Properties view; and the MCP servers. | `abap2ui5.openMode` | `tab` | `tab`, `panel` or `external` | | `abap2ui5.reloadOn` | `activation` | When the preview reloads: `activation`, `save` or `never` | | `abap2ui5.viewCheck.minUi5` | `1.71` | The UI5 version your system runs | -| `abap2ui5.viewCheck.distribution` | `sapui5` | Which distribution the system serves | +| `abap2ui5.viewCheck.distribution` | *(not decided)* | Which distribution the system serves — `sapui5` or `openui5`; undecided, a control from a SAPUI5-only library is a hint | | `abap2ui5.viewCheck.live` | `true` | Run the property gate while typing | | `abap2ui5.viewCheck.render` | `false` | Also run the headless render gate | | `abap2ui5.viewPreview.theme` | `sap_horizon` | Theme the systemless preview renders in | diff --git a/docs/configuration/debugging.md b/docs/configuration/debugging.md index 939dfa54..ebe7f813 100644 --- a/docs/configuration/debugging.md +++ b/docs/configuration/debugging.md @@ -13,9 +13,9 @@ On the frontend, abap2UI5 behaves like a standard UI5 app, so the usual tools an ### Developer Tools Press `Ctrl+F12` to open the built-in **Developer Tools** of abap2UI5: ![Developer Tools showing XML View and Data Model inspection](/configuration/debug.webp) -Tabs cover the whole roundtrip: **Error** and **Log**, the **System** info, the **Previous Request** and **Response**, the app's **Source Code**, and for every view slot (main, popup, popover, nested) the rendered **View** XML and its **Model** data. +Six tabs cover the whole roundtrip: **Overview**, **Problems**, **Roundtrips** (each request and its response), **View & Data** (the rendered XML and the model of the slot you pick — main, popup, popover, nested), **System** and **Search**. -The footer offers **Logout**, **Restart**, a jump to **ADT**, and an **Export** that bundles everything — including the running app's ABAP class source — into one blob you can attach to a bug report. Error popups also carry a copy-to-clipboard button for the same purpose. +The footer offers **Retry**, **Restart**, **Logout**, a jump to **ADT**, **Copy**, **Report a Bug** and an **Export** that bundles everything — including the running app's ABAP class source — into one blob you can attach to a bug report. Error popups also carry a copy-to-clipboard button for the same purpose. ### UI5 Inspector Another option: the SAP default debugging tool, the [UI5 Inspector](https://chromewebstore.google.com/detail/ui5-inspector/bebecogbafbighhaildooiibipcnbngo). diff --git a/docs/configuration/performance.md b/docs/configuration/performance.md index c92950c5..a353a408 100644 --- a/docs/configuration/performance.md +++ b/docs/configuration/performance.md @@ -54,7 +54,7 @@ Want to tune your app further? A few tips: - Call `client->view_display` only when needed — on initialization and when the view structure changes. For a pure data change, set the attribute and return; the framework pushes the delta and UI5 re-renders only the controls that changed. - Bind data with `client->_bind` — the framework sends only the paths the user actually edited back to ABAP (a delta), so read-only and untouched fields cost nothing on the return trip. -- Declare public attributes in your app class only for variables shown on the frontend. This keeps the framework from reading unused values. +- Declare public attributes in your app class only for variables shown on the frontend: public is what becomes part of the model sent to the browser. Everything else is serialized into the draft either way, so keep large data out of the instance rather than merely private. - Follow standard ABAP best practices, like cutting loops and choosing sorted tables, just like in any other ABAP project. ## Performance Issues? diff --git a/docs/configuration/s4_public_cloud.md b/docs/configuration/s4_public_cloud.md index ad815c50..7e172aae 100644 --- a/docs/configuration/s4_public_cloud.md +++ b/docs/configuration/s4_public_cloud.md @@ -77,10 +77,10 @@ Up to here the HTTP service is enough — you can open it and use abap2UI5 with ADT abapGit cannot import a UI5 app (BSP), so this step runs from VS Code with the [SAP Fiori Tools](https://marketplace.visualstudio.com/items?itemName=SAPSE.sap-ux-fiori-tools-extension-pack) extension pack instead. -1. Clone the branch that matches your stack — `cloud_v2` for the current UI5 runtime: +1. Clone the branch that matches your stack — `cloud` for the classic UI5 runtime, `cloud_v2` for the [legacy-free](/advanced/legacy_free) one (UI5 2.x preview): ```sh - git clone --branch cloud_v2 --single-branch https://github.com/abap2UI5/frontend.git + git clone --branch cloud --single-branch https://github.com/abap2UI5/frontend.git ``` 2. In the `app` folder, open the Fiori **Application Info** page and add a **deployment configuration** pointing at your system. Keep the target package name short here too. diff --git a/docs/configuration/security.md b/docs/configuration/security.md index 3f8b3634..76398dba 100644 --- a/docs/configuration/security.md +++ b/docs/configuration/security.md @@ -24,76 +24,71 @@ The frontend is a Single-Page Application (SPA) built with SAPUI5 or OpenUI5. Th abap2UI5 never sends the app's business logic to the client. All business processes stay safely on the server, and sensitive data never reaches the frontend. ## Content-Security-Policy -To strengthen security, abap2UI5 uses a Content Security Policy (CSP) by default. CSP blocks attacks like cross-site scripting (XSS) and data injection by restricting which resources the browser can load. The default policy allows a fixed set of trusted sources — the SAP and OpenUI5 CDNs plus jsDelivr and cdnjs; the complete policy is shown below. It also carries two hardening directives — `object-src 'none'` and `base-uri 'self'` — that block plugin content and pin `` to the app origin. It deliberately carries **no** `frame-ancestors`: browsers ignore that directive in a `` CSP (and log a console warning about it), so cross-origin framing is forbidden by the real `X-Frame-Options` response header instead — see [Response headers](#response-headers) below. +To strengthen security, abap2UI5 uses a Content Security Policy (CSP) by default. CSP blocks attacks like cross-site scripting (XSS) and data injection by restricting which resources the browser can load. The default policy allows a fixed set of trusted sources — the SAP and OpenUI5 CDN hosts, and nothing else; an installation that loads from another host adds it in the user exit, to the one directive that needs it. The complete policy is shown below. It also carries two hardening directives — `object-src 'none'` and `base-uri 'self'` — that block plugin content and pin `` to the app origin. It deliberately carries **no** `frame-ancestors`: browsers ignore that directive in a `` CSP (and log a console warning about it), so cross-origin framing is forbidden by the real `X-Frame-Options` response header instead — see [Response headers](#response-headers) below. -The default **does** contain `'unsafe-eval'`: the ui5loader of OpenUI5 `1.71` — the oldest supported release — still evaluates module source as a string, and without `'unsafe-eval'` a `1.71` bootstrap fails with a CSP `EvalError`. Modern UI5 releases load all modules without `eval()`, so if you pin a modern release you can tighten the policy — see [Hardening: Dropping `'unsafe-eval'`](#hardening-dropping-unsafe-eval) below. +The default carries **no** `'unsafe-eval'` and no `'unsafe-inline'` for scripts: nothing abap2UI5 ships evaluates code, and UI5 from `1.84` on loads its modules without `eval()`. The page's one inline script is allowed by its SHA-256 hash, which the framework appends to `script-src` after the exit ran, so an injected `| - - )->ele( `Page` - )->tag( `Button` - )->a( n = `text` v = `call custom JS` - )->a( n = `press` v = client->_event( `CUSTOM_JS` ) ). - - client->view_display( view->stringify( ) ). - - ENDIF. - - IF client->get( )-event = `CUSTOM_JS`. - client->follow_up_action( `myFunction()` ). - ENDIF. - -ENDMETHOD. -``` - -::: danger Never Inject Untrusted Input -If you must use this, ensure the JavaScript content is **entirely static and hardcoded**. Never concatenate user input, database values, translatable texts, or any other dynamic data into the script string — doing so turns the feature into a direct XSS vulnerability. -::: - -### Embedding JavaScript Directly in an XML View - -::: warning Also Not Recommended -The same security considerations apply: any `