Imported from EduIDE/EduIDE-Landing-Page (
AGENTS.md). Install upstream withnpx skills add EduIDE/EduIDE-Landing-Page. Copyright stays with the author.
AGENTS.md — EduIDE-Landing-Page
The page a student lands on: pick an environment, start a session. A Vite + React SPA served by nginx.
CLAUDE.md is a symlink to this file, so every agent reads the same thing.
Layout
src/App.tsx 576 lines, and effectively the whole application
src/common-extensions/types.ts the EduIDE-specific config surface
src/components/ 15 presentational components
src/sentry.ts reads the config a second time, before React mounts
public/config.js DEVELOPMENT ONLY - see below
nginx.conf serves config.js uncached; SPA fallback for /imprint, /privacy
Nearly all logic is in App.tsx. Config load, URL parsing, Keycloak, workspace
naming, launch and fallback all live there.
Commands
npm run dev # http://localhost:5173
npm run build # tsc && vite build
npm run typecheck
npm run lint # --max-warnings 0, so warnings are fatal
npm run format
There are no tests, no test runner and no test dependency.
Only the build runs in CI. npm run lint, typecheck and format:check
never run — the Docker build's npm run build is the sole gate, so type errors
fail CI and lint and formatting violations do not. Run them yourself.
How configuration reaches the app
Nothing comes from environment variables and nothing is baked in at build time. There is a single mechanism:
index.html loads /config.js -> window.theiaCloudConfig -> getTheiaCloudConfig()
In production that file is a Kubernetes ConfigMap, generated by
EduIDE-Helm's landing-page-config-map.yaml template and mounted over the
image's own copy. public/config.js in this repo therefore ships in the
image and is always shadowed in a real deployment — editing it changes
nothing in production, only local development.
If the config fails validation the app renders a bare
FATAL: Theia Cloud configuration could not be found. That string means a
malformed or missing config.js, nothing else.
The dev config and the chart disagree, and it bites
public/config.js writes appId:; the chart emits serviceAuthToken:. Most
code reads a.serviceAuthToken || a.appId and tolerates either — but the
default-selection validator checks serviceAuthToken only. So ?appDef=<id>
fails locally with Invalid default selection value and works in production.
This is the most likely source of "works in prod, broken locally".
Keys typed here that the chart never emits
pageTitle and sentryDsn, so the title is always the hardcoded default and
Sentry always uses the hardcoded DSN in src/sentry.ts.
The app list is a contract with the chart
config.additionalApps comes from appDefinitions.apps.<name>.landingPage in
the chart. The chart's field is label; it arrives here as appName.
| Field | Behaviour |
|---|---|
visible |
defaults to shown. Only an explicit false hides an app — and a hidden app is still launchable via ?appDef= |
buildSystems |
the picker appears only with 2 or more. With exactly one it is auto-selected silently |
image |
resolves to a logo path; absent means it is derived from appName |
Build-system logos derive from the id, not the label — adding a build
system means adding public/assets/logos/<id>-logo.png.
Selecting a build system changes the workspace name, so Maven and Gradle variants of one app get separate volumes. And a launch with a build system is always workspace-backed, never ephemeral: ephemeral sessions receive their environment through the data bridge, which arrives after the entrypoint has already run.
The build-system picker is skipped entirely when gitUri or artemisToken is
present — the Artemis flow never sets TEMPLATE.
URL parameters are a second config channel
appDef, gitUri, gitUser, gitMail, artemisUrl, artemisToken, user.
They become session environment variables whose names are a contract with the
IDE image's entrypoint, not with this repo.
gitUri is cloned verbatim with no credential injection, so a private repo
needs credentials in the URL. Treat it as secret-bearing.
Auth
Keycloak via keycloak-js, pinned exactly. Silent check-sso on load, then
interactive login on demand.
getCurrentRedirectUri() returns window.location.href, query string
included, deliberately — that is what carries gitUri and artemisToken
through the Keycloak round trip. There is a commit that exists solely to fix
this. Do not simplify it to window.location.origin.
With useKeycloak: false the app invents anonymous-<random> as the user and
suppresses the whole Keycloak UI. That is the default in the checked-in dev
config.
Traps
- Two live logo directories.
public/assets/logos/is used by the app list andindex.html;public/images/logos/is used by exactly one component. Neither is dead. threeandvantainpackage.jsonare not what runs. The background reads globals loaded from a CDN inindex.html, at a different version. Bumping the npm packages has no runtime effect; the animated background also depends on outbound CDN access at runtime.- Module-level mutable state guards one-time initialisation, and hooks are called after an early return behind an eslint disable. Fragile under StrictMode's double invocation. Do not restructure casually.
index.htmlpoints its Open Graph and Twitter meta tags at a test host.- The footer hardcodes a version string; the real one comes from the chart.
- The legal pages are hardcoded TUM/GDPR content, partly German. Content, not code, and legally sensitive.
Conventions
- Prettier with 4-space indent for TS/TSX/CSS.
.editorconfigsays 2 and is wrong; Prettier wins because it is whatnpm run formatruns. simple-import-sortis an error, so imports must be sorted — which is why files begin with the CSS side-effect import.no-nullis an error. Returnundefined, or add the disable comment the existing components use foruseRef(null).- Explicit return types on non-expression functions; it is a warning, and warnings are fatal.
- Release tags are
vX.Y.Z; the image tag is the same string without thev. eslint-plugin-headeris installed but no header rule is configured. There are no license banners; do not add them expecting the linter to want them.
