This file follows the cross-tool AGENTS.md convention and is the single agent instruction file of this repository.
CLAUDE.mdnext to it is a pointer at this file, nothing more.
The source of the npm package @abap2ui5/embed-control
(packages/embed-control): the UI5 custom control
z2ui5.embed.Container, which runs an abap2UI5 app - an ABAP class
implementing z2ui5_if_app - inside any UI5 app. Nothing else: the examples
that consume the package - a UI5 freestyle app, Fiori elements apps for
OData V4 and V2 (with the RAP service the latter reads) and a UI
Integration Card for SAP Build Work Zone - their Playwright tests and the
branches that deliver them live in
abap2UI5/samples-embed-control,
where they take the package from npm. CI here runs them with the control of
the commit (see "The examples are tested here, but live there").
Language: English for all code, comments, docs, commit messages, PRs.
The abap2UI5 frontend - the z2ui5 UI5 component the control wraps - is
not part of this repository or the package. The control loads it at run
time from the abap2UI5 service it talks to
(GET <endpoint>?z2ui5-bundle, answered by z2ui5_cl_ui5_http_handler from
the generated z2ui5_cl_ui5f_preload; its only source is
abap2UI5 app/webapp).
- Never vendor, copy or pin the frontend here, and add nothing to the package that has to follow abap2UI5's releases.
- A change the control needs from the frontend (an embedded mode, a new
componentDatasetting, ...) is a pull request to abap2UI5. Do not work around a frontend limitation in the control when the fix belongs there - say so instead. - Nothing is deployed to an ABAP system for the control: it is a plain module (no component, no library, no manifest), so it never takes an app index entry, and any number of apps carry their own copy.
| Path | |
|---|---|
packages/embed-control/src/ |
The control (Container.js) and its stylesheet |
packages/embed-control/ui5.yaml |
UI5 CLI project of type module: /thirdparty/z2ui5/embed/ → src/ |
packages/embed-control/README.md |
The consumer documentation - what npm shows |
packages/embed-control/CHANGELOG.md |
Every release; the publish checks it |
scripts/consumer-check.mjs |
The packed package in an app of its own, built with UI5 CLI 3 and 4 |
scripts/release-check.mjs |
The gate before npm publish: tag, version, changelog and repository agree |
test/container.test.mjs |
The Node tests of the control: its module run with stubs for UI5 - no browser, no backend (npm test) |
.github/workflows/ |
ci.yaml (checks, consumer builds, and the examples of samples-embed-control with the control of the commit - their branch build and their e2e tests against abap2UI5's default branch and the 1.145.0 floor - on every pull request, every night, and before every publish), publish.yaml (npm, on a GitHub release) |
abap2UI5/samples-embed-control
is the source of the examples, their tests (test/e2e/,
playwright.config.mjs) and their branch build (scripts/build-bsp.mjs).
The samples job of ci.yaml checks out its main, runs npm ci there,
unpacks npm pack of packages/embed-control into its
node_modules/@abap2ui5/embed-control - the one copy every example takes -
and runs npm run bsp and npx playwright test there.
- Never add an example, a test app or a copy of the examples here. A
test of the control is a test in samples-embed-control, against one of its
examples; a new example goes there too. The one exception needs no
browser:
test/container.test.mjsruns the control's module in Node with stubs for UI5 and pins what the control decides before UI5 is involved - the endpoint check and what is requested, what the component gets, when a start happens, is repeated or given up. A case of that kind goes there; everything that renders or talks to a backend goes over there. - The floor leg names samples-embed-control's Playwright projects and
its tag
@after-1.145.0. A project renamed over there is renamed inci.yamlhere in the same breath. - A control change that needs an example to change - new behaviour a
test pins down differently - needs both sides: the examples over there must
pass with the published control and with the one of this
main. Land the control first when the examples cannot do both, and say so in the pull request. - An example only reaches the control's new features with its release: samples-embed-control takes the package from npm, by its lockfile.
- UI5 1.71 is the floor, as in abap2UI5. Use no module, class, property or
enum newer than 1.71, and no
sap/ui/core/Lib/sap/ui/core/Elementstatic APIs. What the control uses today and since when:sap/ui/dom/includeStylesheet(1.58),sap/base/Log(1.58),Component.create(1.56),ComponentContainer#lifecycle(1.56), rendererapiVersion: 2(1.67). The e2e tests run the freestyle example on 1.71 too (samples-embed-control'sfreestyle/ui5-1.71.yaml, theui5-1.71Playwright project) - a change tosrc/is done when every project of thesamplesjob passes, thefiori-elementsandfiori-elements-v2ones included. - Keep the control thin. It picks the class, the endpoint and the size;
everything the app does comes from the backend through the component. It
configures the component only through what the frontend reads itself -
componentData(abap2UI5Component.init), filled from the bundle'sz2ui5/embedmodule plusstartupParametersandendpoint- never by patching the manifest or reaching into the component's state. - The bundle is code - load it only from a path on this server. Keep the
sameOriginUrlcheck: theendpointis parsed the way the browser parses it (new URL(endpoint, location.href)), refused before anything is requested unless it is http(s) on the page's origin, and only the resolved absolute URL is requested and handed to the frontend - never the raw string, which the URL parser rewrites (it drops tabs and line breaks, reads a backslash as a slash), and never a bare path, which the browser resolves against the page's<base>- another host, possibly. A path that begins with//is refused too: the parser drops.and..segments, so/.//hostcomes out on the page's origin with the path//host, another host wherever that path is used on its own. Load it with a<script src>, never witheval,new Functionorfetch+ inject. - One frontend per page, one component per control, one backend session per
component. The bundle is loaded once; a change of
app,endpointorparamsreplaces the component, and so doesrestart(); nothing is patched into a running one. A start is compared by what the backend gets- the resolved endpoint, the params as
startupParametersmakes them - never by the raw property values. A control that shared a load from another control's endpoint and saw it fail tries its own once; a failed start is never repeated by the control itself.
- the resolved endpoint, the params as
widthandheightgo to the live DOM, not through a rendering (_setSizeProperty): a re-rendering of the control re-renders the ComponentContainer and with it every control of the app, and UI5 1.71 rebuilds their DOM - the value the user was typing and the focus went with it. A property the ComponentContainer does not draw from is written withsetProperty(name, value, true)and applied to the DOM the same way.- Nothing starts for a control nobody sees. UI5 calls
onBeforeRenderingfor an invisible control too; the control starts only whilevisible, and an app that runs keeps running while it is hidden. - Every failure reaches the host asynchronously, from the promise chain
after the rendering that started the app - the refused endpoint included.
Never fire
componentFailedorcomponentCreatedinside a rendering hook. thirdparty/, notresources/. An app deployed to an ABAP system answers every<app>/resources/path from the system's UI5; the control is served and built underthirdparty/z2ui5/embed/and registered with a relative resourceRoot. Keepui5.yaml, the consumer README and the examples' manifests in samples-embed-control in step.z2ui5/embedis the backend's module,z2ui5/embed/this package's namespace. The bundle defines the module; the control's own modules live below it. Never add a module namedz2ui5/embedhere, and a folderapp/webapp/embed/in abap2UI5 would collide with this namespace.- No inline styles for descendants and no
eval: a host with a strict Content-Security-Policy must need nothing extra. Styles go intoContainer.css, scoped under.z2ui5EmbedContainer. - A UI5 module id is case-sensitive and a wrong one only fails in the browser
(
includeStylesheet, notincludeStyleSheet) - run the e2e tests.
- The package's
ui5.yamlstays at specVersion 3.0. The consumer's UI5 CLI reads it, and UI5 CLI 3 refuses a dependency with 4.0.npm run consumer:checkbuilds the packed package with CLI 3 and 4. - abap2UI5 1.145.0 is the backend floor - the first release that answers
?z2ui5-bundle. The package records it in itspackage.json("abap2ui5": { "minBackend": "1.145.0" }, the custom-field style@abap2ui5/node-runtimeuses), the package README names it in prose, with what 1.145.0 does not do yet, and the samples job ofci.yamlruns the examples against it (the published@abap2ui5/node-runtimeof that version, read from the field by the jobfloor) next to abap2UI5 main; raise it in package.json and the README - and in samples-embed-control'sci.yaml, which tests the published control - when the control starts to rely on something newer. A test that needs a newer backend is tagged@after-<floor>(@after-1.145.0today), which the floor leg leaves out, as it leaves out the Fiori elements examples, which need the hash. The package has noengines: it is a browser control without Node code, and a UI5 CLI 3 consumer may install it on Node 16 or 18. - A host that routes by the hash needs abap2UI5 1.146.0. Its
bundle marks the component embedded (
componentData.embedded, abap2UI5Component.init), and an embedded component leaves the URL hash to the host - 1.145.0 clears it after every roundtrip, which sends a Fiori elements object page back to its list, and takes SAP Build Work Zone off the page a card sits on. The control passes the bundle's component data through and sets no flag of its own. The READMEs name 1.146.0 for it.
npm ci
npm run lint && npm run format:check
npm test # the Node tests of the control (test/), no browser, no backend
npm run pack:check # package contents: ui5.yaml, src/ and CHANGELOG.md (npm adds README, LICENSE)
npm run consumer:check # the tarball in an app of its own, built with UI5 CLI 3 and 4A change to src/ also runs the examples - in a checkout of
samples-embed-control, with this package copied into its
node_modules/@abap2ui5/embed-control (README, "Checks"): npm run bsp
and npx playwright test there, against an abap2UI5 backend with
?z2ui5-bundle on :3000.
All text files are LF-only. The JavaScript, JSON and CSS files are formatted
with Prettier (.prettierrc, npm run format:check); the Markdown files are
not - Prettier would pad every table to its column width.
A GitHub release v<version> publishes the version in
packages/embed-control/package.json - by trusted publishing (OIDC, no
token), after the whole CI passed on that commit (publish.yaml calls
ci.yaml). The steps are in the README; the one-time setup is in the header
of publish.yaml.
- The examples follow with a bump over there. samples-embed-control takes a new version with its lockfile (dependabot, or by hand after the release - with the examples' ranges, once the version leaves them).
- Every release has its section in
packages/embed-control/CHANGELOG.md, and nothing stays under "Unreleased".scripts/release-check.mjsrefuses the publish otherwise, and a tag that does not name the version. repository.urlnames the repository the workflow runs in - npm refuses the provenance otherwise. A renamed repository needs package.json and the Trusted Publisher entry on npmjs.com to follow.- Running
publish.yamlby hand is a dry run. Never publish from a developer machine except the one-time first version (README, "The first version").