Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,15 @@ what matters here:
and the README's package table (what a package plays together with). Adding a
sample or a package therefore needs **no step for the page**: `npm run
samples:md`, and it is on both.
- **The overview app is not on it.** `Z2UI5_CL_SMPS_APP_000` is this same
catalogue inside a system, and as a card it contradicted the page: *needs
nothing beyond abap2UI5*, on a page built around what a sample needs from the
system — plus a technology chip that filtered ten groups down to itself.
`generate-web-index.mjs` drops the class and its group (and
`generate-screenshots.mjs` takes no picture of it), the page names it in *How
to run one* instead, and `SAMPLES.md` and `catalogue.json` keep it — their
reader is in the repository already. So the page shows **31 samples in 9
groups** where the two catalogues show 32.
- **`web/apps.json` is generated and not committed** — `npm run web:index`
writes it, `deploy-web` writes it again on every deploy. A committed copy
would put a diff of derived data on every sample pull request.
Expand All @@ -353,7 +362,7 @@ what matters here:
A view the harness cannot render is reported and skipped, and the page
treats the missing file as "no picture" (the `<img>` removes itself), so a
card without a thumbnail is normal, not broken. Measured over the corpus
(2026-08): **19 of 32 app views render.** The 13 skips are three stable
(2026-08): **18 of 31 app views render.** The 13 skips are three stable
categories — `sap.ui.comp` controls (SAPUI5-only, absent from the
harness's OpenUI5 runtime: 7 of the 9 Smart Controls samples), `z2ui5.cc`
custom controls that do not load headless (the WebSocket, MIME-audio and
Expand Down
16 changes: 10 additions & 6 deletions scripts/generate-screenshots.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* sample pull request carries no binary diff. The page treats a missing
* picture as "no picture" (the <img> removes itself), so this script is
* allowed to skip what it cannot photograph. Measured over the whole corpus
* (2026-08): 19 of 32 app views render; the three skip reasons are stable and
* (2026-08): 18 of 31 app views render; the three skip reasons are stable and
* documented in AGENTS.md §8 - `sap.ui.comp` is SAPUI5-only and not in the
* harness's OpenUI5 runtime, `z2ui5.cc` custom controls do not load headless,
* and the mock model seeds an empty ObjectStatus state. Each skipped card
Expand All @@ -41,7 +41,7 @@ import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { screenshotFiles } from '@abap2ui5/linter';
import { scanSamples } from './lib/scan-samples.mjs';
import { scanSamples, OVERVIEW_CLASS } from './lib/scan-samples.mjs';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const argOut = process.argv.indexOf('--out');
Expand All @@ -63,10 +63,14 @@ const SIZE = { width: 800, height: 600 };
* would take all pictures with it. */
const CHUNK = 25;

/* Every app of the repository, the same scan the page itself is built from -
* helpers (behavior pools, demo data, the APC protocol class) have no card
* and get no picture. */
const apps = scanSamples(ROOT).filter((s) => s.isApp).slice(0, LIMIT);
/* Every app the page shows a card for, the same scan and the same cut the page
* itself is built from - helpers (behavior pools, demo data, the APC protocol
* class) have no card and get no picture, and neither does the overview app,
* which generate-web-index.mjs leaves off the page (it says why). Photographing
* it would cost a render per deploy for a file nothing loads. */
const apps = scanSamples(ROOT)
.filter((s) => s.isApp && s.cls !== OVERVIEW_CLASS)
.slice(0, LIMIT);
fs.mkdirSync(OUT, { recursive: true });

let written = 0;
Expand Down
25 changes: 21 additions & 4 deletions scripts/generate-web-index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@
* cannot work is worse than no link, so the cards link into the SOURCE and
* into the package README that says how to set the sample up.
*
* THE OVERVIEW APP IS NOT ON THIS PAGE. `Z2UI5_CL_SMPS_APP_000` is this same
* catalogue rendered inside a system, and as a card it answered the page's own
* question with a contradiction — *needs nothing beyond abap2UI5*, on a page
* whose premise is that everything here needs something from the system — while
* taking a technology chip of its own that filtered ten groups down to itself.
* It is a way to browse the corpus, not something to browse the corpus for, so
* the page names it in *How to run one* and leaves the cards to the samples.
* Dropped here rather than in web/stack.js: the page draws what it is given,
* and SAMPLES.md and catalogue.json still carry the overview app, because a
* reader of those is inside the repository already.
*
* Where every fact comes from — no fact is restated here, all of them are read
* out of what the repository already keeps:
*
Expand Down Expand Up @@ -56,7 +67,7 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { scanSamples, sampleTitle } from './lib/scan-samples.mjs';
import { scanSamples, sampleTitle, OVERVIEW_CLASS } from './lib/scan-samples.mjs';
import { packages, OVERVIEW_PKG } from './lib/read-packages.mjs';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
Expand Down Expand Up @@ -148,11 +159,18 @@ function abapDoc(source) {

/* `count` is the page's own derived state — apps per group, for the chips —
* so it is added here rather than carried by the shared package merge. */
const byDir = new Map(packages(ROOT).map((p) => [p.dir, { ...p, count: 0 }]));
const byDir = new Map(packages(ROOT)
.filter((p) => p.dir !== OVERVIEW_PKG.dir)
.map((p) => [p.dir, { ...p, count: 0 }]));
const all = scanSamples(ROOT);
const apps = [];

for (const s of all.filter((x) => x.isApp)) {
/* The overview app and its group, left out for the reason at the top of this
* file. It is the only app of `src/` itself, so dropping the class and dropping
* the group are the same cut made twice — and it has to happen before the
* unknown-package check below, which is there to catch an app that landed in a
* subpackage by accident. */
for (const s of all.filter((x) => x.isApp && x.cls !== OVERVIEW_CLASS)) {
const pkg = byDir.get(s.pkg);
/* A subpackage — src/03/01 holds the business object, not an app. If an app
* ever lands in one, the page would silently lose it, so say so instead. */
Expand All @@ -176,7 +194,6 @@ const index = {
repo: REPO,
source: SOURCE,
tree: TREE,
overviewApp: OVERVIEW_PKG.dir,
packages: [...byDir.values()],
apps,
};
Expand Down
6 changes: 5 additions & 1 deletion scripts/lib/read-packages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ import path from 'path';
* branch's own syntax version, and the lowest of those is v740sp08 — so
* 7.40 SP08 is measured, exactly like the numbers in packages.json. Cloud
* likewise: the overview resolves every sample by name at runtime and calls no
* on-premise API, which is what puts it on the cloud-capable branches. */
* on-premise API, which is what puts it on the cloud-capable branches.
*
* The page in web/ is the one consumer that drops this group again — there the
* overview app is not a sample of the stack but the same catalogue in a system,
* and generate-web-index.mjs says why. catalogue.json keeps it. */
export const OVERVIEW_PKG = {
dir: '.',
branch: 'main',
Expand Down
12 changes: 11 additions & 1 deletion scripts/lib/scan-samples.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ export function scanSamples(root) {
return out.sort((a, b) => a.pkg.localeCompare(b.pkg) || a.cls.localeCompare(b.cls));
}

/**
* The overview app — the catalogue of this repository inside a system.
*
* An app like any other to abapGit, to the checks and to SAMPLES.md, and the
* one app the page in web/ leaves out (generate-web-index.mjs says why). The
* two generators behind that page name it from here rather than each keeping
* a copy of the string.
*/
export const OVERVIEW_CLASS = 'z2ui5_cl_smps_app_000';

/*
* The overview app's own catalogue, read back out — for the COMPLETENESS check
* only, no longer as a source of text.
Expand All @@ -150,7 +160,7 @@ export function scanSamples(root) {
*/
const ENTRY = /sample\(\s*no\s*=\s*`([^`]*)`[\s\S]*?title\s*=\s*`([^`]*)`[\s\S]*?detail\s*=\s*`([^`]*)`[\s\S]*?classname\s*=\s*`([^`]*)`/g;

export function scanOverview(root, overviewClass = 'z2ui5_cl_smps_app_000') {
export function scanOverview(root, overviewClass = OVERVIEW_CLASS) {
const file = path.join(root, 'src', `${overviewClass}.clas.abap`);
const text = fs.readFileSync(file, 'utf8');
const byClass = new Map();
Expand Down
13 changes: 13 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ one scan (`scripts/lib/scan-samples.mjs`) so they cannot disagree:
| the package READMEs | somebody who has already picked a technology |
| this page | somebody who has installed nothing and is asking whether their system can run any of it |

**The overview app is the one view that is not itself a card here.** It is this
same catalogue rendered inside a system, so as a card it said *needs nothing
beyond abap2UI5* on a page whose whole premise is that every sample needs
something from the system, and it took a technology chip of its own that
filtered the corpus down to one entry — itself. `generate-web-index.mjs` drops
the class and its group before the page ever sees them, and *How to run one*
names it in prose instead, where a way of browsing belongs. It stays in
[`SAMPLES.md`](../SAMPLES.md) and in
[`catalogue.json`](../catalogue.json), whose readers are inside the repository
already.

That last question is what makes this page different from the two next door.
[samples](https://abap2ui5.github.io/samples/) publishes a learning path,
because *"where do I start"* is what a newcomer arrives with;
Expand Down Expand Up @@ -147,6 +158,8 @@ reported and skipped — the `sap.ui.comp` smart controls (SAPUI5-only, not in
the harness's OpenUI5 runtime) and the `z2ui5.cc` custom controls, mostly —
and the `<img>` removes itself when its file is missing, so a card without a
thumbnail is complete, not broken. AGENTS.md §8 carries the measured count.
The script photographs exactly what the page draws a card for, so it skips the
overview app too — a picture no card loads is a render per deploy for nothing.

## Running it locally

Expand Down
3 changes: 2 additions & 1 deletion web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ <h2>How to run one</h2>
Every card copies that for you. Or start
<code id="overviewapp">Z2UI5_CL_SMPS_APP_000</code>, the overview app — the same
catalogue inside your system, which lists what is installed and greys out what
is not.
is not. It is the one app of this repository with no card above, because it
<em>is</em> this catalogue rather than a sample of the stack.
</li>
</ol>
</section>
Expand Down
29 changes: 17 additions & 12 deletions web/stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@
* APC channel, a launchpad. Each app would open there and fail, so the links
* go to the source and to the package README that says how to set it up.
*
* THE TECHNOLOGY FACET IS CHIPS, not a select: ten values, and seeing all
* ten at once is half the answer to "what is even in this repository".
* THE TECHNOLOGY FACET IS CHIPS, not a select: nine values, and seeing all
* nine at once is half the answer to "what is even in this repository".
*
* THE OVERVIEW APP IS NOT IN apps.json, and nothing here has to know that:
* `Z2UI5_CL_SMPS_APP_000` is this same catalogue inside a system, not a
* sample of the stack, and generate-web-index.mjs leaves it and its group
* out (it says why). So every package here has a branch and a README of its
* own, and every app belongs to one — which is what lets this file draw a
* card and a package without asking whether it is the special one.
*/

/* ------------------------------------------------------------------ state */
Expand Down Expand Up @@ -172,10 +179,9 @@ function drawPackageLine(f) {
els.pkgline.innerHTML = `
<b>${esc(p.title)}</b> — ${md(p.topic)}.
Plays together with ${md(p.needs)}. Runs on ${esc(p.runsOn)}.
${p.dir === DATA.overviewApp ? '' : `
Install it alone from the branch
<a href="${esc(DATA.tree + p.branch)}" target="_blank" rel="noopener"><code>${esc(p.branch)}</code></a>,
or read <a href="${esc(DATA.source + p.readme)}" target="_blank" rel="noopener">its README</a>.`}
Install it alone from the branch
<a href="${esc(DATA.tree + p.branch)}" target="_blank" rel="noopener"><code>${esc(p.branch)}</code></a>,
or read <a href="${esc(DATA.source + p.readme)}" target="_blank" rel="noopener">its README</a>.
${p.note ? `<br><em>${md(p.note)}</em>` : ''}`;
}

Expand Down Expand Up @@ -230,8 +236,7 @@ function card(app, tokens) {
<div class="actions">
<a class="primary" href="${esc(DATA.source + app.file)}" target="_blank" rel="noopener">Source ↗</a>
<a href="${esc(DATA.source + p.readme)}" target="_blank" rel="noopener">Setup ↗</a>
${app.pkg === DATA.overviewApp ? '' :
`<a href="${esc(DATA.tree + p.branch)}" target="_blank" rel="noopener" title="This package alone, for abapGit">Branch ↗</a>`}
<a href="${esc(DATA.tree + p.branch)}" target="_blank" rel="noopener" title="This package alone, for abapGit">Branch ↗</a>
</div>
</article>`;
}
Expand All @@ -246,10 +251,10 @@ function drawPackages() {
<p>${md(p.topic)}</p>
<p><b>Plays together with</b> ${md(p.needs)}</p>
<p class="meta">
${p.dir === DATA.overviewApp ? 'src' : `src/${esc(p.dir)}`} ·
src/${esc(p.dir)} ·
${esc(p.runsOn)} ·
${p.count} sample${p.count === 1 ? '' : 's'}
${p.dir === DATA.overviewApp ? '' : ` · <a href="${esc(DATA.tree + p.branch)}" target="_blank" rel="noopener">${esc(p.branch)}</a>`}
${p.count} sample${p.count === 1 ? '' : 's'} ·
<a href="${esc(DATA.tree + p.branch)}" target="_blank" rel="noopener">${esc(p.branch)}</a>
</p>
${p.note ? `<p class="note">${md(p.note)}</p>` : ''}
</article>`).join('');
Expand Down Expand Up @@ -316,7 +321,7 @@ async function boot() {
for (const app of DATA.apps) app.hay = haystack(app, PKG.get(app.pkg));

els.total.textContent = String(DATA.apps.length);
els.pkgcount.textContent = String(DATA.packages.filter((p) => p.dir !== DATA.overviewApp).length);
els.pkgcount.textContent = String(DATA.packages.length);

buildFacets();
drawPackages();
Expand Down