diff --git a/AGENTS.md b/AGENTS.md
index 5726863..96b6f04 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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.
@@ -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 `` 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
diff --git a/scripts/generate-screenshots.mjs b/scripts/generate-screenshots.mjs
index 2de4a1d..da215b8 100644
--- a/scripts/generate-screenshots.mjs
+++ b/scripts/generate-screenshots.mjs
@@ -20,7 +20,7 @@
* sample pull request carries no binary diff. The page treats a missing
* picture as "no picture" (the
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
@@ -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');
@@ -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;
diff --git a/scripts/generate-web-index.mjs b/scripts/generate-web-index.mjs
index 062c143..ca1e362 100644
--- a/scripts/generate-web-index.mjs
+++ b/scripts/generate-web-index.mjs
@@ -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:
*
@@ -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)), '..');
@@ -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. */
@@ -176,7 +194,6 @@ const index = {
repo: REPO,
source: SOURCE,
tree: TREE,
- overviewApp: OVERVIEW_PKG.dir,
packages: [...byDir.values()],
apps,
};
diff --git a/scripts/lib/read-packages.mjs b/scripts/lib/read-packages.mjs
index 3142f61..ea62fdb 100644
--- a/scripts/lib/read-packages.mjs
+++ b/scripts/lib/read-packages.mjs
@@ -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',
diff --git a/scripts/lib/scan-samples.mjs b/scripts/lib/scan-samples.mjs
index ee5cd64..bb5c3b2 100644
--- a/scripts/lib/scan-samples.mjs
+++ b/scripts/lib/scan-samples.mjs
@@ -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.
@@ -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();
diff --git a/web/README.md b/web/README.md
index b1acb74..f8c6cc1 100644
--- a/web/README.md
+++ b/web/README.md
@@ -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;
@@ -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 `
` 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
diff --git a/web/index.html b/web/index.html
index 926fe8f..d958305 100644
--- a/web/index.html
+++ b/web/index.html
@@ -122,7 +122,8 @@
Z2UI5_CL_SMPS_APP_000, 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
+ is this catalogue rather than a sample of the stack.
diff --git a/web/stack.js b/web/stack.js
index 43d78a5..e0a5c7b 100644
--- a/web/stack.js
+++ b/web/stack.js
@@ -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 */
@@ -172,10 +179,9 @@ function drawPackageLine(f) {
els.pkgline.innerHTML = `
${esc(p.title)} — ${md(p.topic)}.
Plays together with ${md(p.needs)}. Runs on ${esc(p.runsOn)}.
- ${p.dir === DATA.overviewApp ? '' : `
- Install it alone from the branch
- ${esc(p.branch)},
- or read its README.`}
+ Install it alone from the branch
+ ${esc(p.branch)},
+ or read its README.
${p.note ? `${md(p.topic)}
Plays together with ${md(p.needs)}
${p.note ? `${md(p.note)}
` : ''} `).join(''); @@ -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();