From ff66fba72e1dbd21a1af081665d90a81d8be2247 Mon Sep 17 00:00:00 2001 From: Matthias Linhuber Date: Thu, 27 Aug 2026 00:24:40 +0200 Subject: [PATCH 1/2] docs: unorphan seven pages, write the four placeholders, add admin guides Seven instructor pages were live on the site and in no sidebar, so the only way to reach them was to guess the URL. Two of them - what-you-cannot-evaluate and honest-limitations - are the pages that set expectations honestly, and nobody could find either. Docusaurus does not warn about this. It fails the build on a sidebar entry with no page, but publishes a page with no sidebar entry silently. scripts/check-docs.sh now enforces both directions, plus that every page under docs/ belongs to a plugin at all, plus that relative links resolve. It runs in CI. That check immediately found more: docs/intro.md and docs/contributions/ were served by no plugin, so they were never published - and intro.md's six project links all pointed at theia-* names that were renamed to eduide-* months ago. Deleted, along with sidebars.ts, which no plugin has referenced since the site moved to four separate docs instances. Four instructor pages described themselves as mock placeholders in published prose - "This placeholder page will eventually describe...". They are written now: course setup as the split between what the platform team provisions and what the instructor arranges; cohort management as what it actually is, which is capacity planning around concurrent session starts; a live-session playbook including the fact that reloading makes a slow start worse; and where feedback really comes from, which is Artemis, and the fact that unpushed work gets none. New administrator section covering what this rework created and what nothing documented: installing both charts, adding an installation, rollback and its limits, and the version policy. The two things most likely to cost someone a day are called out explicitly - that a certificate omitting a hostname leaves the Gateway reporting healthy, and that a floating image tag makes helm report success without pulling. --- .github/workflows/ci.yml | 34 +++++ docs/admins/install/adding-an-installation.md | 100 +++++++++++++ docs/admins/install/installing.md | 136 ++++++++++++++++++ docs/admins/maintenance/release-policy.md | 65 +++++++++ docs/admins/maintenance/rollback.md | 55 +++++++ docs/contributions/README.md | 55 ------- docs/contributions/_template.md | 77 ---------- .../course-operations/cohort-management.md | 79 +++++++++- .../course-operations/course-setup.md | 66 ++++++++- docs/instructor/teaching/feedback-rhythm.md | 67 ++++++++- .../teaching/live-session-playbook.md | 71 ++++++++- docs/intro.md | 60 -------- scripts/check-docs.sh | 88 ++++++++++++ sidebars.ts | 41 ------ sidebarsAdmins.ts | 7 + sidebarsInstructor.ts | 20 +++ 16 files changed, 760 insertions(+), 261 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/admins/install/adding-an-installation.md create mode 100644 docs/admins/install/installing.md create mode 100644 docs/admins/maintenance/release-policy.md create mode 100644 docs/admins/maintenance/rollback.md delete mode 100644 docs/contributions/README.md delete mode 100644 docs/contributions/_template.md delete mode 100644 docs/intro.md create mode 100755 scripts/check-docs.sh delete mode 100644 sidebars.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b0e9158 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +# Docusaurus fails a build on a sidebar entry with no page, but publishes a page +# with no sidebar entry and says nothing. Seven instructor pages sat live and +# unreachable that way, including the two that set expectations honestly. + +name: CI + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + structure: + name: Pages are reachable and links resolve + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: ./scripts/check-docs.sh + + build: + name: Site builds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build diff --git a/docs/admins/install/adding-an-installation.md b/docs/admins/install/adding-an-installation.md new file mode 100644 index 0000000..8ce6efa --- /dev/null +++ b/docs/admins/install/adding-an-installation.md @@ -0,0 +1,100 @@ +--- +title: Adding an Installation +description: Standing up a new EduIDE installation for a course, department or university. +--- + +# Adding an Installation + +An installation is one namespace on one cluster, with its own hostnames, +branding and identity provider. Adding one is a file, a GitHub Environment and +an approval — not a bespoke deployment. + +## 1. Describe it + +Two files under `environments//` in the deployment repository. They are +split by who reads them: `env.yaml` configures the **deploy** and Helm never +sees it; `values.yaml` configures the **chart** and the workflow never +interprets it. + +`env.yaml` is deploy metadata only: + +```yaml +apiVersion: eduide.dev/v1 +kind: Environment +metadata: + name: bonn + displayName: EduIDE Bonn + tier: production # test | staging | production +spec: + cluster: eduide # must match a file in clusters/ + namespace: eduide-bonn # every namespace carries the eduide- prefix + platform: + chartVersion: 2.0.0 + channel: release # release | main +``` + +`values.yaml` is a plain Helm values file: hosts, Gateway `parentRefs`, Keycloak +and branding. Nothing else — image tags, app definitions, the preload list and +the storage class are all derived or come from the cluster. + +## 2. Create the GitHub Environment + +Named exactly as the directory, holding: + +| Secret | What | +|---|---| +| `KUBECONFIG` | the cluster's kubeconfig | +| `THEIA_KEYCLOAK_COOKIE_SECRET` | `dd if=/dev/urandom bs=32 count=1 \| base64 \| tr -d -- '\n' \| tr -- '+/' '-_'` | + +The admin API token is a repository secret and is inherited. + +Add required reviewers for anything `tier: production` or `staging`. **Do not** +add them to an environment that deploys automatically — the approval gate blocks +the automation outright and the run waits forever. + +## 3. Bootstrap the cluster + +``` +Actions -> Bootstrap cluster -> cluster: , dry_run: true +``` + +This derives the Gateway's listeners, the certificate's hostnames and the +monitored namespaces from every environment that claims the cluster. Adding an +installation therefore needs no second file edited — and cannot end up with a +certificate that omits it, which is exactly how one environment ran for six +months on a certificate covering only its neighbours. + +Read the diff, then run it again with `dry_run: false`. + +## 4. Outside the repositories + +- **DNS** for all four hostnames. +- **Keycloak**: a client matching `clientId`, with redirect URIs for all four + hosts. A wrong realm or client fails at login, not at deploy, so nothing in CI + catches it. +- **The webview wildcard certificate**, which cert-manager cannot issue over + HTTP-01. It goes in the cluster's bootstrap environment as + `THEIA_WILDCARD_CERTIFICATE_CERT` and `_KEY`. + +## 5. Deploy + +``` +Actions -> Deploy (dispatch) -> environment: +``` + +The deploy asserts which cluster it reached before touching anything, shows a +`helm diff` before applying, runs `--wait --atomic`, and prints a summary read +back from the cluster rather than echoed from its inputs. + +## A note on identity + +An installation with no identity provider configured yet must say so explicitly +with `keycloak.allowUnauthenticated: true`. The chart otherwise refuses to +render. + +That flag exists because the oauth2-proxy configuration is emitted regardless of +whether Keycloak is enabled — the operator mounts it into every session pod by +literal name, so it cannot be conditional. Left at the chart's defaults it points +the proxy at a host that does not exist, and sessions fail at the proxy rather +than running unauthenticated. That is the worst of both outcomes, so the chart +makes you choose deliberately. diff --git a/docs/admins/install/installing.md b/docs/admins/install/installing.md new file mode 100644 index 0000000..3477462 --- /dev/null +++ b/docs/admins/install/installing.md @@ -0,0 +1,136 @@ +--- +title: Installing EduIDE +description: Installing the platform on a cluster from the published charts. +--- + +# Installing EduIDE + +EduIDE ships as two Helm charts, published as OCI artifacts to +`ghcr.io/eduide/charts`. They always carry the same version, and that version +is the platform version. + +| Chart | Installed | Owns | +|---|---|---| +| `eduide-cluster` | once per **cluster** | CRDs, the conversion webhook, ClusterRoles, cert-manager issuers, the shared Gateway, PodMonitors and dashboards | +| `eduide` | once per **environment** | operator, REST service, landing page, routes, app definitions, image preloading | + +The split exists because the two halves have different cardinality. CRDs and a +Gateway must exist once on a cluster; a tenant install exists once per namespace, +and there may be several on the same cluster. Installing cluster-scoped +resources from every tenant deploy is what previously made concurrent deploys +race each other. + +## Prerequisites + +The cluster must already have: + +- **cert-manager**, for the conversion webhook's certificate and for issuing + hostname certificates +- **Gateway API CRDs** and a Gateway controller. EduIDE is tested with Envoy + Gateway +- a **storage class** for session volumes + +## Cluster half + +```bash +helm install eduide-cluster oci://ghcr.io/eduide/charts/eduide-cluster \ + --version 2.0.0 -n eduide-system --create-namespace \ + -f cluster-values.yaml +``` + +`cluster-values.yaml` supplies the Gateway's listeners and the certificate +names. In the TUM installations this file is not written by hand — the +`Bootstrap cluster` workflow derives it from the environment manifests, so +adding an environment does not mean remembering to edit a second file. + +## Tenant half + +```bash +helm install eduide oci://ghcr.io/eduide/charts/eduide \ + --version 2.0.0 -n eduide-test1 --create-namespace \ + -f values.yaml -f secrets.yaml +``` + +The chart refuses to install if the cluster chart is absent, rather than letting +the operator crash-loop on a missing CRD. + +## What one version pins + +A bare install with no overrides pins every image. Nothing floats. + +| Value | Source repository | Default | +|---|---|---| +| `versions.ide` | EduIDE — the IDE images | empty, meaning the chart's `appVersion` | +| `versions.cloud` | EduIDE-Cloud — operator and REST service | set by the release | +| `versions.landingPage` | EduIDE-Landing-Page | set by the release | + +They are separate because the three repositories release on different cadences, +and a one-line landing-page fix should not rebuild fifteen multi-gigabyte IDE +images. + +**Never set a single tag for everything.** A pull request only builds the images +of the repository it came from, so pointing all three at `pr-451` puts most of +the namespace into `ImagePullBackOff`. + +## Minimum values + +```yaml +hosts: + configuration: + baseHost: eduide.example.edu + landing: eduide + service: service.eduide + instance: instance.eduide + allWildcardInstances: ["*.webview."] + +keycloak: + enable: true + authUrl: https://sso.example.edu/ + realm: your-realm + clientId: eduide + +gateway: + parentRefs: + - { name: theia-shared-gateway, namespace: eduide-system, sectionName: prod-landing } + - { name: theia-shared-gateway, namespace: eduide-system, sectionName: prod-service } + - { name: theia-shared-gateway, namespace: eduide-system, sectionName: prod-instances } + - { name: theia-shared-gateway, namespace: eduide-system, sectionName: prod-webview } +``` + +Secrets — the Keycloak cookie secret and the admin API token — go in a second +values file, never on the command line. `--set` puts them in the process list +and in Actions debug logs. + +## Four hostnames, not one + +Each installation serves: + +``` + the landing page +service. the REST service +instance. session ingress +*.webview.instance. per-session webviews +``` + +All four need DNS and all four need to be on a certificate. The webview host is +two labels below the instance host, so a wildcard covering the others does not +cover it — it needs its own certificate, and because it is a wildcard, +cert-manager cannot issue it over HTTP-01. + +**A certificate that omits a hostname fails silently.** The Gateway reports the +listener healthy — Gateway API never compares a certificate's names against a +listener's hostname — so the first symptom is a browser warning, and the second +is the landing page being unable to call its own REST service, because the +browser blocks that cross-origin request over an invalid certificate. + +## Verifying + +```bash +kubectl -n get deploy # operator, service, landing-page, garbage-collector +kubectl -n get appdefinitions.theia.cloud +kubectl -n get httproute -o wide +curl -sI https:/// # 200, and TLS must validate without -k +``` + +Then start a real session from the landing page. Everything above can be healthy +while sessions do not start. diff --git a/docs/admins/maintenance/release-policy.md b/docs/admins/maintenance/release-policy.md new file mode 100644 index 0000000..9ee3c38 --- /dev/null +++ b/docs/admins/maintenance/release-policy.md @@ -0,0 +1,65 @@ +--- +title: Release and Version Policy +description: What a version number means here, and how a release is cut. +--- + +# Release and Version Policy + +## One version, one meaning + +| Thing | Form | Example | +|---|---|---| +| git tag | `vX.Y.Z` | `v2.0.0` | +| chart `version` | `X.Y.Z` | `2.0.0` | +| image tag | `X.Y.Z` | `1.2.0` | +| chart `appVersion` | the EduIDE IDE image version | `1.2.0` | + +The chart version is the **platform** version and is what an installation pins. +`appVersion` is the tag of the IDE images, so a chart states exactly which IDEs +it runs. + +The operator, REST service and landing page carry their own versions in +`versions.cloud` and `versions.landingPage`, because they release on a different +cadence. A one-line landing-page fix should not rebuild fifteen multi-gigabyte +IDE images. + +Three spellings of a git tag existed historically — `1.1.0`, `v1.1.0` and +`v.1.1.1` — which is why nothing could reliably answer "which release produced +this image". A shared CI check now rejects anything that is not `vX.Y.Z`. Old +tags are not retagged. + +## Cutting a release + +The release train in EduIDE-Helm does it, and defaults to `dry_run: true`. + +1. **Validate** the version is semver and the tag is unused. +2. **Build every component at that tag** — but tag nothing yet. Building fifteen + large images is the flakiest step in the pipeline; tagging afterwards means a + flake costs a re-run rather than stranding immutable tags on repositories + whose images were never published. +3. **Verify every expected image exists**, for both architectures. The IDE image + list is read from EduIDE's build matrix at run time, not kept by hand — a + hand-kept copy went stale within days and would have let a release verify ten + of twelve images and pass. +4. **Only now tag and release** the component repositories. +5. **Publish both charts** at the same version. +6. **Open a bump pull request** against each production installation. Production + is never deployed automatically. + +## Channels + +An installation is `release` or `main`. + +- `release` pins whatever its values file says. Nothing that happens on `main` + can move a production image. +- `main` resolves an immutable `latest-` tag. Never a floating tag: with a + floating tag the pod template does not change between upgrades, so Helm sees + no difference, reports success without pulling, and `--atomic` has nothing to + roll back. + +## Upgrading an installation + +Change `chartVersion` in that environment's `env.yaml` and open a pull request. +CI renders the change; the diff shows what will happen before anyone approves +it. Production environments require a reviewer on the GitHub Environment, so the +approval is recorded on the run. diff --git a/docs/admins/maintenance/rollback.md b/docs/admins/maintenance/rollback.md new file mode 100644 index 0000000..712213a --- /dev/null +++ b/docs/admins/maintenance/rollback.md @@ -0,0 +1,55 @@ +--- +title: Rollback +description: Undoing a deploy that succeeded but behaves badly. +--- + +# Rollback + +Deploys run with `--wait --atomic`, so a deploy that **fails** already rolls +itself back. This page is for the other case: the deploy succeeded, the pods are +healthy, and the release is wrong. + +## Rolling back + +``` +Actions -> Rollback -> environment: +``` + +or directly: + +```bash +helm rollback eduide -n --wait +``` + +Helm restores the previous revision. `helm history eduide -n ` shows +what you are going back to. + +## What a rollback does not touch + +**Sessions, workspaces and their volumes survive.** They are custom resources and +PersistentVolumeClaims that the release does not own the lifecycle of, so a +rollback does not disturb anyone's work. A student in a session will usually not +notice. + +**CRDs are not rolled back.** They belong to `eduide-cluster`, not to the tenant +release. A rollback of the tenant chart cannot undo a CRD change. + +That second point is the important one. If a release changed a CRD's stored +version, rolling back the tenant chart leaves the new CRD in place with an older +operator that may not understand it. **A CRD-breaking change needs a major +version bump and a written runbook, not a rollback.** + +## When rollback is the wrong tool + +| Situation | Do this instead | +|---|---| +| The images are wrong but the chart is fine | Redeploy with the correct `versions.*` | +| A certificate does not cover a hostname | Fix the certificate; the release is not the problem | +| A CRD changed incompatibly | Follow the release's runbook. Rolling back the tenant chart will not help | +| The cluster chart is wrong | Roll back `eduide-cluster` in `eduide-system`, and understand that this affects **every** installation on the cluster | + +## Afterwards + +A rollback is a statement that the deployed version was wrong. Say so somewhere +durable — an issue on the release — or the same version gets deployed again next +week. diff --git a/docs/contributions/README.md b/docs/contributions/README.md deleted file mode 100644 index 6de38f4..0000000 --- a/docs/contributions/README.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Student Contributions -sidebar_label: Overview -sidebar_position: 1 ---- - -# Student Contributions to EduIDE - -This section showcases the individual contributions of team members working on the EduIDE ecosystem. Each student documents their thesis work and other significant contributions to the project. - -## Contributing Your Documentation - -To add your own contribution page: - -1. **Copy the template**: Use `_template.md` in this directory as your starting point -2. **Name your file**: Use your name in kebab-case (e.g., `john-doe.md`) -3. **Fill in your information**: Replace all placeholder text with your actual contributions -4. **Add to sidebar**: Your page will automatically appear in the Contributions section - -## What to Include - -### Thesis Work -- Clear title and overview of your research -- Key contributions and achievements -- Technologies and tools you used -- Repositories where your work lives -- Measurable results and impact - -### Other Contributions -- Bug fixes and improvements -- Documentation and guides -- Code reviews and collaboration -- Infrastructure and tooling enhancements - -## Template Structure - -The template includes sections for: - -- **Thesis**: Your main research work and findings -- **Other Contributions**: Additional valuable work beyond your thesis -- **Documentation & Resources**: Guides, presentations, and materials you've created -- **Timeline**: A chronological view of your work -- **Acknowledgments**: Recognition of those who helped you - -## Tips for Good Documentation - -- **Be specific**: Include links to PRs, issues, and commits where possible -- **Show impact**: Explain not just what you did, but why it mattered -- **Use visuals**: Add diagrams, screenshots, or graphs if they help tell your story -- **Keep it updated**: Come back and update your page as you make new contributions -- **Link to projects**: Reference the main project pages in the Projects section - -## Questions? - -If you need help with your contribution page, reach out to the team or check existing contribution pages for examples. diff --git a/docs/contributions/_template.md b/docs/contributions/_template.md deleted file mode 100644 index 13802d9..0000000 --- a/docs/contributions/_template.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: [Your Name] -sidebar_label: [Your Name] ---- - -# [Your Name] - Contributions to EduIDE - -## Thesis - -### Title -[Your thesis title here] - -### Overview -[Brief overview of your thesis work - 2-3 paragraphs describing the main research question, approach, and goals] - -### Key Contributions -- [Main contribution 1] -- [Main contribution 2] -- [Main contribution 3] - -### Technologies Used -- [Technology 1] -- [Technology 2] -- [Technology 3] - -### Repositories -- [Repository name](link) - [Brief description of your work in this repo] -- [Repository name](link) - [Brief description of your work in this repo] - -### Results & Impact -[Describe the outcomes of your thesis work - what was achieved, what impact it had on the EduIDE ecosystem] - ---- - -## Other Noteworthy Contributions - -### [Contribution Title 1] -**Repository**: [Repository link] -**Description**: [Detailed description of this contribution] -**Impact**: [What problem did this solve or what improvement did it bring] - -### [Contribution Title 2] -**Repository**: [Repository link] -**Description**: [Detailed description of this contribution] -**Impact**: [What problem did this solve or what improvement did it bring] - -### [Contribution Title 3] -**Repository**: [Repository link] -**Description**: [Detailed description of this contribution] -**Impact**: [What problem did this solve or what improvement did it bring] - ---- - -## Documentation & Resources - -### Written Documentation -- [Document/Guide title](link) - [Brief description] - -### Presentations & Demos -- [Presentation title](link) - [Brief description] - -### Related Issues & Pull Requests -- [#123 Issue/PR title](link) - [Brief description] - ---- - -## Timeline - -| Period | Activity | -|--------|----------| -| [Month Year - Month Year] | [Activity description] | -| [Month Year - Month Year] | [Activity description] | - ---- - -## Acknowledgments -[Optional: Acknowledge collaborators, mentors, or resources that were particularly helpful] diff --git a/docs/instructor/course-operations/cohort-management.md b/docs/instructor/course-operations/cohort-management.md index b375f24..9694373 100644 --- a/docs/instructor/course-operations/cohort-management.md +++ b/docs/instructor/course-operations/cohort-management.md @@ -1,15 +1,80 @@ --- title: Cohort Management -description: Mock cohort management guide for instructors. +description: How the platform behaves with a whole cohort on it at once, and what that means for scheduling. --- # Cohort Management -This mock page represents the workflow for managing groups, enrollment, and access policies during a term. +There is no cohort object in EduIDE. Students are not enrolled, grouped or +managed here — that all lives in Artemis. What this page is really about is +capacity: what happens when a lot of people use the platform at the same +moment, and how to schedule around it. -## Typical tasks +## The one number that matters: concurrent starts -- Review enrollment sync status -- Assign tutorial groups -- Adjust access for late registrations -- Coordinate teaching assistants +Steady-state load is easy. The hard case is 200 students clicking **Open online +IDE** within the same five minutes, because each one needs a container +scheduled, a volume attached and an IDE booted. + +The platform mitigates this in two ways, both of which have limits: + +**Pre-pulled images.** Every node keeps the IDE images on local disk, so +starting a session does not download several gigabytes first. This is why a +newly added language is slow for the first students to use it — the image has to +reach every node before it helps. + +**Warm sessions.** A configurable number of sessions can be kept running and +idle, so the first students to arrive get one immediately. Once the warm pool is +exhausted, everyone after that waits for a cold start. + +The size of that pool is set per environment by the platform team. If you know a +lab will start at a fixed time, tell them in advance — it can be raised for the +day. + +## Practical scheduling + +- **Stagger where you can.** Two labs of 100 at different hours cost far less + than one of 200. +- **Ask students to start the IDE a few minutes before the exercise**, not at + the moment you begin talking. +- **Expect the first session of the day to be slower.** Idle sessions are + reclaimed overnight. +- **Do not have everyone reload when it feels slow.** A reload abandons the + session that was starting and asks for another. + +## Session limits + +Each student may hold a small number of concurrent sessions — the exact number +is configured per environment. This exists to stop one person accumulating +sessions across devices and browser tabs and exhausting capacity for everyone. + +If a student cannot start a session and is told they have too many, the fix is +to close the ones they have forgotten about, not to raise the limit. + +## Sessions end; workspaces do not + +An idle session is shut down after a timeout. The **workspace** — the student's +files — survives, and reopening the exercise gives it back. + +This distinction matters for how you talk to students: + +- "Your session was closed" means the running IDE stopped. Reopen it. +- Work that was **committed and pushed** is safe regardless. +- Work that was only saved in the editor lives in the workspace volume, which + persists — but this is not a backup, and it is not somewhere to keep the only + copy of anything that matters. + +Tell students to commit and push at the end of every session. That is also what +Artemis grades, so the habit is the same one the course wants anyway. + +## Exams and assessed labs + +Treat a high-stakes session as an operational event, not a normal day: + +- Warn the platform team well in advance, with the exact time and headcount. +- Have a fallback that does not need the platform. If the network between the + lecture theatre and the cluster fails, no amount of capacity helps. +- Do not schedule the first-ever use of a new language image for an exam. + +See [Limitations](../limitations/honest-limitations.md) for what the platform +does not promise. diff --git a/docs/instructor/course-operations/course-setup.md b/docs/instructor/course-operations/course-setup.md index 1d5467e..5628876 100644 --- a/docs/instructor/course-operations/course-setup.md +++ b/docs/instructor/course-operations/course-setup.md @@ -1,15 +1,67 @@ --- title: Course Setup -description: Mock course setup guide for instructors. +description: What to arrange before your first session, and who arranges it. --- # Course Setup -This placeholder page will eventually describe how instructors create a course shell, configure exercises, and prepare cohorts. +Most of the setup for an EduIDE course is not done by you. The platform team +provisions the environment; your work is deciding what students need in it and +making sure the Artemis side lines up. -## Mock setup stages +## What the platform team needs from you -1. Create the course workspace -2. Import assignments and starter material -3. Configure grading and release dates -4. Invite teaching staff +Ask for these before the semester starts, not in the first week. + +| They need to know | Because | +|---|---| +| Which languages your exercises use | Each one is a separate IDE image that has to be offered and pre-pulled onto the cluster nodes | +| Roughly how many students, and when they will all arrive at once | Determines how many sessions are kept warm. A lab where 200 students start within ten minutes behaves very differently from steady use | +| Whether you need a starter template | Template images ship skeleton projects and a build-system choice. Without one, students start in an empty workspace | +| Which Artemis course this belongs to | The integration is per-course | + +Lead time for a new language image is realistically weeks, not days. If your +exercises need something not already offered, raise it early. + +## What is already available + +The platform offers a fixed set of environments, each corresponding to a +language image. Some are plain — an empty workspace with the toolchain +installed — and some ship starter templates with a build-system choice, such as +Maven or Gradle for Java, Make or Bazel for C. + +Ask the platform team which are enabled for your installation. The list differs +between installations, and building an image does not automatically make it +available. + +## What you set up yourself + +**In Artemis.** Create the programming exercise as usual. The EduIDE +integration is a property of the exercise's repository, so nothing separate has +to be configured per student. + +**Test the whole path yourself, as a student would.** Open your own exercise +from Artemis, let the IDE start, make a change, commit and push, and confirm the +result appears in Artemis. Do this before you tell 200 people to do it. The +first start on a cold environment is much slower than later ones, and it is +better that you discover that than a lecture theatre does. + +**Decide what "getting stuck" looks like.** Students will hit problems the +platform cannot solve for them — a push rejected because they edited the wrong +branch, a session that timed out overnight. Decide in advance who they ask. + +## Before the first lab + +- Confirm with the platform team that your environment is up and that the + languages you need are offered. +- Run through one exercise end to end yourself. +- Tell students that the first start is slow and the second is not, so they do + not all reload in the first minute and make it worse. +- Read [Limitations](../limitations/honest-limitations.md), and set expectations + from it rather than discovering them live. + +## What this page does not cover + +Grading, release dates, exercise import and teaching-staff invitations are all +Artemis features and are documented there. EduIDE is the environment the +exercise opens in; it does not manage the course. diff --git a/docs/instructor/teaching/feedback-rhythm.md b/docs/instructor/teaching/feedback-rhythm.md index b550e8d..466f352 100644 --- a/docs/instructor/teaching/feedback-rhythm.md +++ b/docs/instructor/teaching/feedback-rhythm.md @@ -1,15 +1,68 @@ --- title: Feedback Rhythm -description: Mock feedback guidance for instructors. +description: Where feedback actually comes from, and what EduIDE changes about it. --- # Feedback Rhythm -This placeholder page can later document consistent expectations for turnaround times, review channels, and office-hour support. +EduIDE does not grade anything and does not give feedback. Artemis does. What +EduIDE changes is how quickly a student can act on the feedback they get, and +that is worth designing for. -## Mock guidance +## The loop -- Publish weekly feedback windows -- Use reusable rubric comments -- Separate conceptual and technical feedback -- Track unresolved learner blockers +``` +edit in EduIDE -> commit and push -> Artemis builds and tests -> result + ^ | + +----------------------------------------------------------------+ +``` + +The push is the only handoff. Everything before it is local to the workspace and +invisible to Artemis; everything after it is Artemis's business. + +This has one practical consequence that dominates all others: **students who do +not push get no feedback.** Not delayed feedback — none. The most common cause +of "the system didn't tell me anything" is work that never left the workspace. + +## What to say in week one + +- Commit and push at the end of every session, even if the work is unfinished. +- Pushing is how you ask for feedback. It is not a submission ceremony. +- The IDE keeps your files between sessions, but only pushed work reaches + Artemis. + +Say it again in week two. + +## What EduIDE makes easier + +**No environment excuse.** Everyone has the same toolchain, so "it works on my +machine" stops being a category of feedback you have to give. The failure a +student sees locally is the failure the grader sees. + +**A shorter path from feedback to fix.** The IDE is already open on the exercise; +acting on a test failure does not require rebuilding a local setup first. In a +lab, that can turn a week-long loop into a ten-minute one. + +**You can look at exactly what they have.** Because the environment is uniform, +"send me a screenshot" is usually enough to diagnose. There is no hidden local +state. + +## What it does not change + +- **Test quality.** Feedback is only as good as the exercise's tests. EduIDE + makes it faster to receive, not better. +- **Turnaround.** Build queues and test duration are Artemis's, unchanged. +- **Manual review.** If your course gives written feedback, that workload is + the same. + +## A rhythm that works + +| When | What | +|---|---| +| During a lab | Push early and often; treat the first push as a smoke test, not a submission | +| End of every session | Push, unconditionally | +| Between sessions | Students act on automated results themselves | +| Weekly | You look at aggregate results, not individuals — the common failure across a cohort is usually an exercise problem, not thirty independent student problems | + +That last row is the one people skip. When most of a cohort fails the same test, +the exercise is telling you something. diff --git a/docs/instructor/teaching/live-session-playbook.md b/docs/instructor/teaching/live-session-playbook.md index 01d0923..16909f0 100644 --- a/docs/instructor/teaching/live-session-playbook.md +++ b/docs/instructor/teaching/live-session-playbook.md @@ -1,15 +1,72 @@ --- title: Live Session Playbook -description: Mock teaching playbook for live sessions. +description: Running a lab on EduIDE, and what to do when something goes wrong in front of everyone. --- # Live Session Playbook -This page is reserved for high-signal guidance instructors need during active labs, tutorials, and workshops. +What to do in the room, in the order you will need it. -## Mock playbook +## Before -- Open the session dashboard before class starts -- Monitor stuck workspaces and failed launches -- Broadcast clarifications to all learners -- Escalate systemic issues to admins +- Open the exercise yourself, from Artemis, on the room's network. Not at home + the night before — the network between this room and the cluster is part of + the system. +- Have the exercise URL ready to paste. Do not make 200 people navigate. +- Know who to contact if the platform is down, and have that open already. + +## Starting everyone at once + +Say this, in this order: + +1. Open the exercise in Artemis and click **Open online IDE**. +2. It will take a minute or two the first time. That is normal. +3. **Do not reload.** Reloading abandons the session that is starting and puts + you at the back of the queue. + +That third point is the one that saves the session. A room full of people +reloading turns a slow start into a much slower one. + +If a student is asked to log in and then lands nowhere, they should complete the +login, go back to the Artemis exercise page, and click **Open online IDE** +again. This is a known first-login quirk, not a failure. + +## During + +**"My terminal is gone" / "the IDE looks frozen."** Reload the browser tab. The +session is server-side; the tab is just a view of it. This is the one time +reloading is the right answer. + +**"I can't push."** Almost always a git problem, not a platform problem — wrong +branch, nothing committed, or a conflict. Treat it as you would in any other +environment. + +**"My session closed."** It timed out from inactivity. Reopening the exercise +gives the workspace back. Files are not lost by a timeout. + +**"I lost my work."** Distinguish immediately: pushed work is in Artemis and is +safe. Unpushed work lives in the workspace volume and is almost certainly still +there when they reopen. Work that was never saved in the editor is gone, exactly +as it would be locally. + +## When it is not one student + +If several people report the same failure at once, stop diagnosing individuals. + +1. Try it yourself. If it fails for you too, it is the platform. +2. Tell the room what you know in one sentence, and give them something to do + that does not need the IDE. +3. Contact the platform team with: which environment, what time it started, and + what the error says. "It's broken" costs a round trip. + +**Have a fallback.** For an assessed session, decide in advance what happens if +the platform is unavailable, and say it out loud at the start so nobody +panics. A browser-based IDE has a hard dependency on the network and the +cluster; that is the trade for not having 200 local setups to debug. + +## After + +- Tell students to commit and push before they leave. That is what Artemis + grades and what survives everything. +- If something went wrong, report it even if it resolved itself. A transient + failure nobody reports is one nobody fixes. diff --git a/docs/intro.md b/docs/intro.md deleted file mode 100644 index af682b4..0000000 --- a/docs/intro.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -sidebar_position: 1 -slug: / ---- - -# Welcome to EduIDE - -**EduIDE** is a scalable, cloud-native educational IDE platform built on [Eclipse Theia](https://theia-ide.org/) and integrated with the [Artemis](https://github.com/ls1intum/Artemis) learning management system. It provides students with powerful, browser-based development environments for programming exercises without requiring local installations. - -## What is EduIDE? - -EduIDE delivers a complete IDE experience directly in the browser, enabling students to: - -- Write, compile, and test code without installing tools locally -- Access consistent development environments across devices -- Benefit from intelligent code completion and language server features -- Work with real-world development tools in a managed, scalable infrastructure - -## Architecture Overview - -The EduIDE ecosystem consists of several interconnected components: - -- **[Theia Deployment](./projects/theia-deployment.md)**: Infrastructure-as-code for deploying and managing Kubernetes clusters -- **[EduIDE](./projects/eduidec.md)**: Custom IDE container images with language support and tooling -- **[Theia LSP Extension](./projects/theia-lsp-extension.md)**: Language server integration for intelligent code features -- **[Theia Shared Cache](./projects/theia-shared-cache.md)**: Distributed caching layer for improved performance -- **[Theia Scale Tests](./projects/theia-scale-tests.md)**: Load testing framework for performance validation - -## Key Features - -### 🚀 Cloud-Native Architecture - -Built on Kubernetes with [Theia Cloud](https://github.com/eclipse-theia/theia-cloud), enabling horizontal scaling and efficient resource management. - -### 🎓 Educational Focus - -Designed specifically for university programming courses with features like automated grading integration, exercise templates, and student workspace isolation. - -### 🔧 Extensible Platform - -Supports multiple programming languages (Java, Python, C, Rust, and more) with customizable IDE blueprints and configurations. - -### 📊 Observability - -Comprehensive monitoring with Prometheus and Grafana dashboards for tracking system health and user activity. - -## Getting Started - -To explore the EduIDE projects, browse the **Projects** section in the sidebar. Each project page includes: - -- An overview of the component's purpose -- Key features and capabilities -- Technical implementation details -- Links to source repositories - -For deployment and infrastructure details, start with the [Theia Deployment](./projects/theia-deployment.md) documentation. - -## Student Contributions - -EduIDE is developed and maintained by a team of dedicated students. Check out the **Contributions** section to learn about individual thesis work and contributions to the ecosystem. Each student documents their research, implementations, and the impact of their work on the platform. diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh new file mode 100755 index 0000000..9410324 --- /dev/null +++ b/scripts/check-docs.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Every page must be reachable, and every sidebar entry must exist. +# +# Seven instructor pages were written and never added to a sidebar, so they were +# live on the site but reachable only by guessing the URL. Two of them - +# what-you-cannot-evaluate and honest-limitations - are the most +# credibility-building pages here, and nobody could find either. +# +# Docusaurus does not warn about this. It warns about a sidebar entry with no +# file, but a file with no sidebar entry is silently published and orphaned. + +set -uo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +FAILED=0 + +# plugin docs root : its sidebar file +PLUGINS=( + "docs/developer:sidebarsDeveloper.ts" + "docs/instructor:sidebarsInstructor.ts" + "docs/admins:sidebarsAdmins.ts" + "docs/student:sidebarsStudent.ts" +) + +echo "=== every page is in a sidebar, every sidebar entry has a page ===" +for pair in "${PLUGINS[@]}"; do + base="${pair%%:*}"; sb="${pair##*:}" + [[ -d "$base" ]] || { echo " FAIL $base does not exist"; FAILED=1; continue; } + [[ -f "$sb" ]] || { echo " FAIL $sb does not exist"; FAILED=1; continue; } + + ids=$(grep -oE "'[a-zA-Z0-9_./-]+'" "$sb" | tr -d "'" | sort -u) + # -E: BSD sed does not take \? in a basic expression, and silently leaves the + # extension on, which makes every page look orphaned. + files=$(find "$base" \( -name '*.md' -o -name '*.mdx' \) \ + | sed -E "s|^$base/||; s|\.mdx?$||" | sort -u) + + orphans=$(comm -23 <(echo "$files") <(echo "$ids")) + if [[ -n "$orphans" ]]; then + echo " FAIL $base: page(s) in no sidebar, so unreachable except by URL:" + sed 's/^/ /' <<<"$orphans" + FAILED=1 + fi + + # A sidebar id that names no file. Docusaurus fails the build on these, but + # catching it here names the file instead of the plugin. + dangling=$(comm -13 <(echo "$files") <(echo "$ids") | grep '/' || true) + if [[ -n "$dangling" ]]; then + echo " FAIL $sb: entries with no page:" + sed 's/^/ /' <<<"$dangling" + FAILED=1 + fi + + [[ -z "$orphans" && -z "$dangling" ]] && \ + echo " PASS $base ($(wc -l <<<"$files" | tr -d ' ') pages)" +done + +echo +echo "=== no page is served by zero plugins ===" +served=$(for pair in "${PLUGINS[@]}"; do find "${pair%%:*}" \( -name '*.md' -o -name '*.mdx' \); done | sort -u) +all=$(find docs \( -name '*.md' -o -name '*.mdx' \) | sort -u) +stray=$(comm -13 <(echo "$served") <(echo "$all")) +if [[ -n "$stray" ]]; then + echo " FAIL under docs/ but served by no plugin, so not published at all:" + sed 's/^/ /' <<<"$stray" + FAILED=1 +else + echo " PASS every page under docs/ belongs to a plugin" +fi + +echo +echo "=== relative links resolve ===" +broken=0 +while IFS= read -r f; do + # ](./x) and ](../x) and ](x.md) - site-absolute and external links are not + # checked here; Docusaurus resolves those itself. + while IFS= read -r link; do + [[ -n "$link" ]] || continue + target="$(dirname "$f")/${link%%#*}" + [[ -e "$target" || -e "${target}.md" || -e "${target}.mdx" ]] && continue + echo " FAIL $f -> $link" + broken=1 + done < <(grep -oE '\]\((\.\.?/[^)]+|[A-Za-z0-9_-]+\.mdx?)\)' "$f" 2>/dev/null | sed 's/^](//; s/)$//') +done < <(find docs -name '*.md' -o -name '*.mdx') +[[ $broken -eq 0 ]] && echo " PASS relative links resolve" || FAILED=1 + +echo +[[ $FAILED -eq 0 ]] && echo "ALL PASS" || echo "SOME FAILED" +exit $FAILED diff --git a/sidebars.ts b/sidebars.ts deleted file mode 100644 index cdecaf6..0000000 --- a/sidebars.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; - -// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) - -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - */ -const sidebars: SidebarsConfig = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [ - 'intro', - { - type: 'category', - label: 'Projects', - items: [ - 'projects/theia-deployment', - 'projects/theia-scale-tests', - 'projects/theia-shared-cache', - 'projects/theia-lsp-extension', - 'projects/theia-data-bridge', - 'projects/eduidec', - 'projects/theia-arc-runners', - 'projects/theia-workspace-garbage-collector', - ], - }, - { - type: 'category', - label: 'Contributions', - items: [{type: 'autogenerated', dirName: 'contributions'}], - }, - ], -}; - -export default sidebars; diff --git a/sidebarsAdmins.ts b/sidebarsAdmins.ts index 8b6563c..ca9d4c6 100644 --- a/sidebarsAdmins.ts +++ b/sidebarsAdmins.ts @@ -3,6 +3,11 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; const sidebars: SidebarsConfig = { adminsSidebar: [ 'intro', + { + type: 'category', + label: 'Install', + items: ['install/installing', 'install/adding-an-installation'], + }, { type: 'category', label: 'Platform', @@ -36,6 +41,8 @@ const sidebars: SidebarsConfig = { label: 'Maintenance', items: [ 'maintenance/upgrades', + 'maintenance/rollback', + 'maintenance/release-policy', ], }, ], diff --git a/sidebarsInstructor.ts b/sidebarsInstructor.ts index ed94cab..a7a2c25 100644 --- a/sidebarsInstructor.ts +++ b/sidebarsInstructor.ts @@ -1,5 +1,12 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; +/** + * Every page under docs/instructor must appear here. Docusaurus warns about a + * sidebar entry with no file, but publishes a file with no sidebar entry and + * says nothing - so seven pages sat live and unreachable, including the two + * that set expectations honestly. scripts/check-docs.sh enforces both + * directions now. + */ const sidebars: SidebarsConfig = { instructorSidebar: [ 'intro', @@ -10,6 +17,8 @@ const sidebars: SidebarsConfig = { 'course-evaluation/course-fit', 'course-evaluation/evaluating-eduide-in-a-pilot', 'course-evaluation/customizable-features', + 'course-evaluation/what-you-can-evaluate', + 'course-evaluation/what-you-cannot-evaluate', ], }, { @@ -17,6 +26,17 @@ const sidebars: SidebarsConfig = { label: 'Prerequisites', items: ['prerequisites/course-requirements', 'prerequisites/operational-dependencies'], }, + { + type: 'category', + label: 'Running a Course', + items: ['course-operations/course-setup', 'course-operations/cohort-management'], + }, + { + type: 'category', + label: 'Teaching', + items: ['teaching/live-session-playbook', 'teaching/feedback-rhythm'], + }, + 'limitations/honest-limitations', ], }; From 508a58713b962dccf372243f23dbed2687de9d16 Mon Sep 17 00:00:00 2001 From: Matthias Linhuber Date: Thu, 27 Aug 2026 13:59:30 +0200 Subject: [PATCH 2/2] fix: address review - reconcile provisioning.md, and harden the docs check The review found that the new install pages contradict docs/admins/platform/provisioning.md on three points. They do, and the stale one is provisioning.md: it names deploy-production.yml and deploy-pr.yml, both deleted in EduIDE-deployment#113, and describes staging as an automatic push-to-main deploy with no approval. Staging is manual dispatch now, and e2e-test is the automatic one - which is precisely why e2e-test must NOT have required reviewers, since an approval gate would block it forever. That table is rewritten against the seven workflows that actually exist. That was my omission: I added an install section without reconciling the pages already in the admin sidebar, so the section contradicted itself. installing.md now says plainly that its Helm commands are the manual path for a first install or an unavailable pipeline, and that in normal operation the deploy workflow generates secrets.yaml on the runner from GitHub Environment secrets - it is never written by hand and never reaches the repository. Two real holes in check-docs.sh, both reproduced before fixing: a double-quoted sidebar id was skipped by the extraction, so its page looked orphaned and failed this job for no reason the dangling-id check filtered on '/', so a missing ROOT-level page - exactly the shape of 'intro' - passed silently Ids are now read with label: and type: values stripped first, which catches both quote styles and root-level entries. Also gave the flow diagram a language, for MD040. --- docs/admins/install/installing.md | 11 ++++++++ docs/admins/platform/provisioning.md | 30 ++++++++++++++------- docs/instructor/teaching/feedback-rhythm.md | 2 +- scripts/check-docs.sh | 15 ++++++++--- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/docs/admins/install/installing.md b/docs/admins/install/installing.md index 3477462..f785608 100644 --- a/docs/admins/install/installing.md +++ b/docs/admins/install/installing.md @@ -32,6 +32,10 @@ The cluster must already have: ## Cluster half +These two commands are the manual path. Day to day, `bootstrap-cluster.yml` and +`deploy.yml` run them for you — see +[Provisioning](../platform/provisioning.md). + ```bash helm install eduide-cluster oci://ghcr.io/eduide/charts/eduide-cluster \ --version 2.0.0 -n eduide-system --create-namespace \ @@ -101,6 +105,13 @@ Secrets — the Keycloak cookie secret and the admin API token — go in a secon values file, never on the command line. `--set` puts them in the process list and in Actions debug logs. +**In normal operation you do not write that file.** The deploy workflow +generates it on the runner from the environment's GitHub Environment secrets and +it never reaches the repository. The commands on this page are for a first +install on a new cluster, or for manual intervention when the pipeline is +unavailable — in which case write `secrets.yaml` yourself, keep it out of git, +and delete it afterwards. Everything non-secret belongs in `values.yaml`. + ## Four hostnames, not one Each installation serves: diff --git a/docs/admins/platform/provisioning.md b/docs/admins/platform/provisioning.md index f8a6bbc..ce87268 100644 --- a/docs/admins/platform/provisioning.md +++ b/docs/admins/platform/provisioning.md @@ -9,17 +9,29 @@ This page covers the steps required to bootstrap a new EduIDE environment. It is ## How deployments work -All EduIDE environments are deployed through GitHub Actions pipelines defined in the deployment repository. The pipelines run `helm upgrade --install` for each chart with the environment-specific values files. You do not run Helm commands manually in normal operation — you trigger or configure the pipeline. +All EduIDE environments are deployed through GitHub Actions pipelines defined in +the deployment repository. You do not run Helm commands manually in normal +operation — you trigger or configure the pipeline. For a first install on a new +cluster, or emergency manual intervention, see +[Installing EduIDE](../install/installing.md). -The three deployment workflows are: - -| Workflow | Trigger | Approval required | +| Workflow | Trigger | Approval | |---|---|---| -| `deploy-production.yml` | Manual dispatch | Yes | -| `deploy-staging.yml` | Push to main | No | -| `deploy-pr.yml` | PR push | Yes | - -For emergency manual intervention (e.g., when a pipeline is unavailable), the underlying Helm commands are documented in the steps below. +| `deploy.yml` | Reusable; called by the others | Inherited from the environment | +| `deploy-e2e.yml` | Automatic, follows `main` | **None** — an approval gate would block it forever | +| `deploy-staging.yml` | Manual dispatch | Yes | +| `deploy-dispatch.yml` | Manual dispatch, any environment | Yes for `production` and `staging` | +| `deploy-comment.yml` | `/deploy ` on a pull request | Yes for `production` and `staging` | +| `bootstrap-cluster.yml` | Manual dispatch, once per cluster | Yes | +| `rollback.yml` | Manual dispatch | Yes | + +`e2e-test` is the environment that follows `main` and is tested automatically; +`staging` is the manual one, where a human puts something to look at before it +goes near production. Approval is a property of the GitHub Environment, not of +the workflow — which is why `e2e-test` deliberately has no required reviewers. + +The pre-restructure workflows `deploy-production.yml`, `deploy-pr.yml` and +`deploy-theia.yml` no longer exist. ## Prerequisites diff --git a/docs/instructor/teaching/feedback-rhythm.md b/docs/instructor/teaching/feedback-rhythm.md index 466f352..edbed46 100644 --- a/docs/instructor/teaching/feedback-rhythm.md +++ b/docs/instructor/teaching/feedback-rhythm.md @@ -11,7 +11,7 @@ that is worth designing for. ## The loop -``` +```text edit in EduIDE -> commit and push -> Artemis builds and tests -> result ^ | +----------------------------------------------------------------+ diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh index 9410324..7ceabc7 100755 --- a/scripts/check-docs.sh +++ b/scripts/check-docs.sh @@ -28,7 +28,15 @@ for pair in "${PLUGINS[@]}"; do [[ -d "$base" ]] || { echo " FAIL $base does not exist"; FAILED=1; continue; } [[ -f "$sb" ]] || { echo " FAIL $sb does not exist"; FAILED=1; continue; } - ids=$(grep -oE "'[a-zA-Z0-9_./-]+'" "$sb" | tr -d "'" | sort -u) + # Both quote styles. A double-quoted id was previously skipped, which made its + # page look orphaned and failed this job for no reason. + # + # `label:` and `type:` values are stripped first, so what remains is document + # ids - including root-level ones like `intro`, which carry no slash and were + # previously exempt from the dangling check entirely. + ids=$(sed -E "s/(label|type)[[:space:]]*:[[:space:]]*('[^']*'|\"[^\"]*\")//g" "$sb" \ + | grep -oE "'[a-zA-Z0-9_./-]+'|\"[a-zA-Z0-9_./-]+\"" \ + | tr -d "'\"" | sort -u) # -E: BSD sed does not take \? in a basic expression, and silently leaves the # extension on, which makes every page look orphaned. files=$(find "$base" \( -name '*.md' -o -name '*.mdx' \) \ @@ -42,8 +50,9 @@ for pair in "${PLUGINS[@]}"; do fi # A sidebar id that names no file. Docusaurus fails the build on these, but - # catching it here names the file instead of the plugin. - dangling=$(comm -13 <(echo "$files") <(echo "$ids") | grep '/' || true) + # catching it here names the file instead of the plugin. Root-level ids count: + # filtering on '/' let a missing top-level page through silently. + dangling=$(comm -13 <(echo "$files") <(echo "$ids") || true) if [[ -n "$dangling" ]]; then echo " FAIL $sb: entries with no page:" sed 's/^/ /' <<<"$dangling"