Files
Classroom_Desktop/docs/classroom-v2-experiment.md
T
ekklesiaandClaude Opus 5 d5cc6e463f fix: keep the DVD player whole
Removing gtk-layer-shell from the dependencies broke classroom-dvd on the
first run, and deleting the dock's stylesheet took the DVD control bar's
styling with it. Both go back: the template under a name that says what
still uses it, and the dependency with a line in the package description
explaining that it is there for the disc player and not for a dock.

A probe overlay confirms gtk-layer-shell works under KWin, so the loading
cover survives the move. The focus helper still speaks sway, which is
written down rather than papered over.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 09:10:08 -05:00

583 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Classroom v2 architecture experiment
An open re-evaluation of the whole Ekklesia Classroom desktop stack, with
reliability as the first requirement. Nothing here reaches a stable, canary
or hyprland room, and nothing here changes the development host.
## Base
| | |
| --- | --- |
| branch | `experiment/classroom-v2` |
| base commit | `aadb1b881977b0eb25e1ca10e060977bed31a48f` |
| base subject | *fix: keep the system print dialog in front of the window that rendered it* |
| `VERSION` at base | `0.1.44` |
That commit is the published **stable** build, verified rather than assumed:
`repo/stable/manifest.json` names `ekklesia-classroom_0.1.44_amd64.deb` with
sha256 `7de568…81041`, and the copy in `repo/packages/` hashes to exactly
that. The branch is not built on `experiment/hyprland`; `git ls-files` on it
contains no Hyprland file.
## The answer, in one paragraph
Every user-visible failure the church reported is a piece of desktop
infrastructure the classroom wrote itself. The shell that disappeared had no
supervisor. The minimised window with no feedback exists because wlroots has
no minimise and the classroom invented one. The secondary window that fights
the browser needed a 314-line arbiter because sway tiles. The unrecognisable
browser icon was hand-drawn to match a monochrome set. Debian 13 already
ships a desktop that solves all four, so **the recommendation is Plasma 6.3
on the Debian the classroom already runs**, configured rather than written,
and it was built and tested here rather than argued for.
## The development host, as found
Recorded because it bounds what could be tested.
| | |
| --- | --- |
| OS | Debian 13 trixie (13.5), kernel 6.12.90 |
| machine | already a VM — root is `/dev/vda2`, GPU is `bochs-drm`, CPU is `QEMU Virtual CPU` |
| nested virtualisation | **none** — `/dev/kvm` does not exist |
| libvirt / qemu / VirtualBox | not installed |
| containers | Docker 26.1.5, cgroup v2 |
| resources | 6 vCPU, 11 GiB RAM, 160 GiB free |
**This session could not create a virtual machine**, so the candidate desktop
was given a systemd container instead. Nothing outside Docker was changed on
the host: no packages installed, no configuration touched, no service
altered. See *Alternate operating systems* for what still needs a real VM.
## The lab
`tools/lab/` builds a Debian 13 container with a full Plasma 6.3 desktop in
it, runs systemd as PID 1 so the session is supervised the way a real machine
supervises it, and nests the session inside a headless sway so that `grim`
can photograph it and a real pointer can be aimed at it.
```
tools/lab/lab build # the image
tools/lab/cold-start 3 # three complete cold starts, from nothing
tools/lab/lab session # compositor + Plasma session
tools/lab/lab shot NAME # docs/screenshots/NAME.png
tools/lab/lab click X Y # a real pointer press
tools/lab/kwin-script FILE # ask KWin what it believes about a window
```
Every window measurement in this document came from `kwin-script`, not from
looking at a screenshot. Every click came through libinput.
Three lab-only deviations, recorded because they bound what was proven:
* **Software rendering.** llvmpipe throughout, with KWin nested in sway. This
proves behaviour and layout, not performance on classroom hardware.
* **No sound card and no optical drive.** The audio policy was therefore
tested as a pure function against the real room's reported hardware state
rather than end to end, and DVD playback was not exercised.
* **Video did not decode.** VLC came up fullscreen at exactly 1920×1080 with
the panel covered, which is the window-management result being tested, but
the picture was black under software XWayland.
## What was measured
Each of these changed a decision.
### The panel gives up after three crashes
Plasma's own unit restarts a crashed `plasmashell`, but with
`StartLimitBurst=3` inside sixty seconds. Measured:
| | |
| --- | --- |
| kill 1 | back in 0.3 s |
| kill 2 | back in 0.4 s |
| kill 3 | **never came back** |
That is the production failure the church already had, reproduced on stock
Plasma. A four-line systemd drop-in
(`systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf`) makes
the restart unconditional. Re-measured: **eight consecutive `SIGKILL`s one
second apart, back every time in about 2.4 s**, with the open application
window untouched.
This is the single most important line in the whole experiment, and it is a
supported override rather than a supervisor of our own.
### KWin already recovers, and takes nothing with it
`kill -9 kwin_wayland` twice: back in **0.1 s** each time, via
`kwin_wayland_wrapper`, with the running application still on screen and its
Wayland connection intact.
### A deleted panel rebuilds itself
`plasma-org.kde.plasma.desktop-appletsrc` was deleted and the shell
restarted. The classroom bar came back exactly as designed, from the layout
script in the look-and-feel package. So the recovery procedure for a damaged
desktop is one file removed and one service restarted — no reflash, and no
technician in the room.
### Plasma cannot show all five application states
The brief asks for closed, running, active, minimised and behind. Measured by
sampling the pixels of the indicator strip rather than by eye:
| state | indicator | icon |
| --- | --- | --- |
| closed | none — panel background `(26, 29, 32)` | full colour |
| running, behind | grey `(42, 45, 48)` | full colour |
| running, active | teal `(65, 127, 121)` | full colour |
| **minimised** | grey `(42, 45, 48)` — **identical to behind** | full colour |
Three marks for four states. Rather than write a task manager that draws a
fourth — which is exactly the kind of custom desktop code this experiment
exists to remove — **the classroom removes the state**: clicking a launcher
always means *show me this*, never *hide it*, and the window decoration has
no minimise button. There is nothing on the idle desktop a teacher could want
to see, so minimising in this room only ever loses the lesson.
A window minimised by some other route is still shown as running and comes
back with one click, so it can never be lost even then.
### A window rule cannot tell a browser from its popup
The first attempt maximised the browser with a KWin rule on `google-chrome`.
Measured: the window a lesson page opened was maximised too, because Chrome
gives both the same class — the very behaviour the current architecture
spends 314 lines preventing.
Chrome can tell them apart, so `--start-maximized` in the launcher does it.
Measured afterwards:
| | |
| --- | --- |
| main browser | `[0,0] 1920×1004` — the whole desktop above the bar |
| window the page opened | `[509,160] 902×683` — centred, the size the page asked for |
| main browser afterwards | unchanged |
The secondary window carries an ordinary title bar with a close button, so
the dock's sticky Close button has nothing left to do.
### Printing
A page's own Print button produced a **native KDE print dialog** through
`xdg-desktop-portal-kde`, `transient=true` on the browser, centred over it,
in the classroom's colours, offering only **Elmer**. Stacking is the window
manager's ordinary behaviour and needed no rule.
### An immutable colour scheme cannot be toggled
Marking `[General]` immutable in `/etc/xdg/kdeglobals` froze the palette so
hard that `classroom-theme light` changed the running session and lost the
change at the next start. Immutability now covers the Kiosk restrictions and
nothing else. Measured both ways afterwards.
### Two packages cannot own one file
`dpkg -i` refused the first package outright: `plasma-workspace` already
owns `/etc/xdg/plasmanotifyrc`, and `kdeglobals`, `kwinrc` and the rest are
the same story. The room's settings live in
`/etc/ekklesia-classroom/xdg/` instead, with
`/etc/environment.d/90-ekklesia-classroom.conf` putting that directory in
front of `/etc/xdg` in `XDG_CONFIG_DIRS`. That is KConfig's own cascade
rather than a fight over a path, and it was checked at runtime:
`systemctl --user show-environment` carries it, and `kreadconfig6` in the
session reads the classroom's values.
The `postinst` then failed on a `chmod` for a command that had been
removed. It walks what the package actually installed now.
### VLC calls itself `Vlc`
KWin's window matching is case sensitive, and a lower-case substring rule
matched nothing while reporting nothing. The rule is a regular expression
now.
## The architecture
| | |
| --- | --- |
| distribution | **Debian 13 trixie** — unchanged, so the `.deb` and OTA work survives |
| desktop | **KDE Plasma 6.3.6** (Debian's own packages) |
| compositor | **KWin 6.3.6**, Wayland |
| panel | **Plasma panel**, one full-width bar, built by a look-and-feel layout script |
| task feedback | **Icons-only Task Manager** |
| notifications | **Plasma's own** |
| browser | **Google Chrome**, unchanged |
| Wonder Ink | Chrome app mode, **its own profile** |
| session start | **SDDM autologin** (recommended; not yet tested — see risks) |
| lockdown | **KDE Kiosk** action restrictions |
| audio | one small classroom service, event driven |
### The bar
One continuous full-width bar, 76 px, the height the room already has.
* **Left** — Parent Call, drawn as a line mark in the accent rather than a
colour tile, so it reads as a different kind of thing from the applications
and is visibly not one of them.
* **Centre** — the applications: Wonder Ink, and the browser wearing
**Chrome's own icon, in colour**. That is the direct answer to the teacher
who asked whether the old monochrome glyph meant "the Internet".
* **Right** — network, volume, removable media, notifications, clock. Four
applets placed directly on the bar rather than a system tray: the tray
discovers whatever is installed and hides most of it behind an arrow, and
in the lab it collected battery, brightness, clipboard, keyboard layout,
camera, vaults and screen management on a machine with none of those.
The bar launches applications, shows their state, and shows a little status.
It is not the Live View, not Parent Call, and not a shell framework.
### Wonder Ink as an application
```
Wonder Ink chrome --app=https://app.wonderink.org/ --user-data-dir=…/wonderink
Browser chrome the room's own profile
```
Its window class is `chrome-app.wonderink.org__-Default` — measured on a
running session, and the launcher's `StartupWMClass` matches it exactly, so
the panel shows one button rather than a launcher and a window side by side.
The separate profile buys something the current single-profile design cannot:
if a teacher signs into Wonder Ink through Google, that Google session is in
the Wonder Ink profile and closing the window ends it, while the church
YouTube sign-in lives in the browser's profile and is untouched. The
documented conflict in `browser.md` — that clearing `wonderink.org` leaves
the next teacher one click from the previous teacher's account — does not
arise.
The ordinary browser therefore no longer opens Wonder Ink. Its home page is
YouTube, because a second way in through that window would put a teacher's
lesson session in the room's shared profile, which is the leak the split
exists to prevent.
### Windows
KWin's ordinary behaviour, with one rule.
* Main windows fill the desktop above the bar, edge to edge, because Chrome
is told to start maximized.
* Windows a page opens float over the main one, centred, at the size the page
asked for, with a title bar and a close button.
* Dialogs are transient on their parent and stack correctly with no help.
* Fullscreen video covers the whole display: `[0,0] 1920×1080`, panel
included, no rounding, no wallpaper.
* One rule, and it is `noborder` for VLC.
**The outer margin and the 10 px rounding were dropped.** Chrome squares its
own corners when maximised, so margin and rounding are the same decision, and
expressing "the screen minus 16 px" needs a rule generated per resolution at
every boot. That is glue in exchange for appearance, and the brief says
reliability wins. Full-bleed above the bar is also what every other appliance
does.
### Theme
One set of design tokens in `EkklesiaDark.colors`; the light scheme is
**generated** from it by `tools/build-light-scheme`, so a colour can only be
changed in one place. Plasma applies either to the panel, the notifications,
the decorations and every dialog at once. `classroom-theme light|dark|toggle`
is the whole switch. Accent teal `#4FB3A8`, taken from the wallpaper.
### Wallpaper
`config/wallpapers/classroom-calm.png` is a **stand-in**, generated by
`tools/build-wallpaper` in the sage/teal/blue/lavender palette with a river
leading into the picture and a quiet upper middle. The illustrated wallpaper
the church selected is not in this repository; when it arrives it replaces
that file and nothing else changes.
### Lockdown
`/etc/xdg/kdeglobals`, `[KDE Action Restrictions][$i]`. Verified by clicking,
not by reading the file: right-clicking the wallpaper produced no menu,
right-clicking the bar produced no menu, and KRunner was not running and
could not be summoned. `run_command`, `shell_access`, `logout`,
`action/lock_screen`, `action/kwin_rmb` and `plasma/plasmashell/unlockedDesktop`
all read back `false`. There is no lock screen and no session restore.
Nothing was achieved by deleting a binary. Lifting a restriction for
maintenance is one line and a session restart.
### Audio
The one place custom code is still right, because no desktop expresses this
policy. `lib/classroom_audio.py` is a pure function so it can be tested
against the room that failed rather than against a machine:
* a port that reports itself unavailable is not a candidate;
* an analog port existing is not evidence that a cable is in it;
* an explicit `AUDIO_OUTPUT=hdmi|analog` overrides everything.
`scripts/classroom-audio` follows PipeWire's own event stream — no polling —
and on every change sets the card profile, sets the default sink, and
**moves streams that are already playing**. That last step is the fix for the
teacher who had to restart the browser after changing the output.
Twelve test cases in `tests/test-classroom-audio.sh`, the real room among
them. The service is supervised the same way the panel is; measured, it came
back from four consecutive kills.
### DVD
**How much of the DVD player survives this change was measured, not
assumed**, because the room's disc playback works and breaking it for
tidiness would be a bad trade.
* Its loading cover and control bar are `gtk-layer-shell` surfaces. KWin
implements `zwlr_layer_shell_v1` — the Plasma panel is one — and a probe
overlay came up full-screen under it. So the DVD surfaces keep working
and `python3-gi` and `gir1.2-gtklayershell-0.1` stay in the package.
Removing them broke `classroom-dvd toggle` immediately, which is how this
was found.
* Its stylesheet was the dock's. The dock is gone; the template survives as
`config/overlay/style.css.in`, renamed to say what still uses it.
* `classroom-dvd` calls `classroom-window`, which speaks sway's IPC.
Measured with no sway running: it exits 0 and does nothing, so it
degrades rather than hanging — but the DVD player's focus handling is not
yet ported, and that is the largest piece of work this change leaves.
* One behavioural difference: under sway the loading cover hid the dock;
under KWin the panel draws above it, so the bar stays visible while a
disc spins up. Fullscreen VLC still covers everything, which is the state
that matters.
`/usr/share/solid/actions/ekklesia-dvd.desktop` with an
`X-KDE-Solid-Predicate` for a video DVD. Plasma's device notifier offers
"Play the DVD" while a disc is in the drive and withdraws it when the disc
leaves, so a room without a drive shows nothing and no room needs
`DVD_ENABLED` edited. Nothing polls, and `classroom_optical`'s two-second
loop goes away. **Not exercised** — the lab has no optical drive.
## Custom code this deletes
Deleted on this branch, and counted rather than estimated:
| | lines | replaced by |
| --- | --- | --- |
| `scripts/classroom-dock` | 754 | the Plasma panel |
| `scripts/classroom-chrome-windows` | 314 | `--start-maximized`, and a window manager |
| `tests/test-classroom-chrome-windows.sh` | 280 | — |
| `config/dock/style.css.in` | 102 | the colour scheme |
| `scripts/classroom-notifications` | 74 | Plasma notifications |
| `config/sway/config` | 62 | `kwinrc` |
| `config/mako/config.in` | 48 | `plasmanotifyrc` |
| `config/waybar/*` | 55 | the Plasma panel |
| `config/bash_profile`, getty autologin | 6 | SDDM |
| `config/icons/browser.svg` | — | Chrome's own icon |
| **total** | **1 695** | |
Rewritten rather than deleted: `classroom-browser` (41 → 30 lines, it no
longer decides launch-or-focus-or-minimise), `classroom-scale` (sway output
scale → `kscreen-doctor`), `classroom-notify` (found the session by looking
for sway and refused to run without mako; now looks for the compositor and
the shell).
**Still present, and still sway-shaped.** `classroom-window`,
`classroom_sway.py`, `classroom_icons.py`, `classroom_style.py` and
`classroom_optical.py` are all reachable only from the DVD player and the
MQTT notifier, and they were deliberately not rewritten — see *the DVD
player* under unresolved risks. Another ~590 lines go when that is done.
Kept because they are the classroom's own subject matter: the DVD session
and CSS setup, printer setup, provisioning, MQTT, the updater, shutdown.
Added: the audio policy and its test, the theme switch, and configuration.
The package was built and its contents checked: every `/usr/local` path any
classroom command names is in it.
## Alternatives, and why they lost
**Stay on sway, add Waybar.** Cheapest migration, and it loses on the thing
that matters. wlroots has no minimise, so the invented hidden workspace
stays; sway tiles, so the 314-line window arbiter stays; there is no
window-manager decoration, so the dock's sticky Close button stays; and
Waybar is one more unsupervised process to lose. It removes none of the four
reported problems.
**Hyprland** (`experiment/hyprland`). Already measured on this repository and
already carrying two findings against it: the legacy config silently matches
nothing, and a Lua config changes the IPC language so the classroom would be
adopting a brand-new format early. For critical production UI in a room
nobody attends on a Sunday morning, that is the wrong end of the maturity
curve.
**GNOME.** Task state, a bottom bar and application pinning all need
extensions, and extensions break on GNOME updates. That is precisely the
"several fragile independently-maintained components" the brief rules out.
**Fedora KDE / Arch.** Both would supply the same Plasma. Neither supplies
anything Debian 13 does not, and both would cost the entire `.deb` and OTA
investment. Arch's rolling updates are the wrong risk for an appliance that
must survive unattended. **Changing distribution buys nothing here**, which
is the most useful negative result in this document.
**Firefox ESR / Chromium instead of Chrome.** No concrete benefit. Firefox
has no app mode, which is what makes Wonder Ink an application; Debian's
Chromium ships no Widevine. Chrome is what Wonder Ink is known to work in.
## Reliability results
| test | result |
| --- | --- |
| cold start, 3 identical runs | 16, 16, 17 s — panel, compositor and 11 applets every time |
| `plasmashell` SIGKILL ×8 at 1 s | back every time, ≈2.4 s |
| `plasmashell` on stock settings | **dead on the third kill** — fixed by the drop-in |
| `kwin_wayland` SIGKILL ×2 | back in 0.1 s, windows survived |
| audio service SIGKILL ×4 | active every time |
| panel configuration deleted | rebuilt itself from the layout script |
| right-click desktop / panel | no menu |
| KRunner | not running, not reachable |
| light ⇄ dark | persists both ways |
| memory, 3 samples over 6 minutes | flat: plasmashell 399 MB, KWin 141 MB under llvmpipe |
| package install on a clean machine | clean, and the session came up from it |
## The package
`tools/build-release.sh` builds it, and it was installed on a fresh machine
and booted from rather than described. `ekklesia-classroom_0.1.45` is 65
files: the desktop under `/etc/ekklesia-classroom/xdg`, a look and feel
package, two colour schemes, the launchers, the Solid action, two supervised
user services, and the classroom's own commands. Dependencies swap
`sway, swaybg, waybar, mako-notifier, python3-gi, gir1.2-gtklayershell-0.1`
for `plasma-desktop, plasma-workspace, kwin-wayland, plasma-nm, plasma-pa,
sddm`, and it `Conflicts` with the welcome tour, the software centre and the
phone pairing daemon that `plasma-desktop` would otherwise recommend into a
classroom.
Checked mechanically: every `/usr/local` path any classroom command names is
in the package.
The OTA mechanism is untouched. This is an ordinary version on the ordinary
channel structure, and an experimental build would go out as
`0.1.45~plasma1` on a channel of its own, exactly as the Hyprland milestone
did.
## Screenshots
In `docs/screenshots/`.
`v2-idle-desktop` the idle desktop · `v2-light-bar` / `v2-dark-bar` the same
bar in both themes · `state-1-closed` closed · `state-3-behind` active beside
running · `state-4-minimized` · `state-5-restored` · `v2-testpage` a
maximised browser · `v2-secondary-window` a window a page opened, floating ·
`v2-print-dialog` · `v2-notification-crop` · `v2-fullscreen` · `recover-panel`
the bar rebuilt from nothing · `lock-desktop-rmb`, `lock-panel-rmb`,
`lock-krunner` the lockdown · `v2-from-package` the desktop as installed
from the `.deb` · `v2-layershell` a gtk-layer-shell overlay under KWin, which
is what the DVD player's loading cover is made of.
## Not done, and honest about it
* **DVD end to end.** The overlay surfaces and disc detection were checked;
playback was not, and the focus helper is not ported. See *the DVD
player* above.
* **Real audio hardware.** Policy tested as a function; no card to apply it to.
* **Video decode and performance.** Software rendering only.
* **SDDM.** Recommended over the current `bash_profile` `exec`, because when
the session ends SDDM starts another one instead of leaving a teacher at a
shell prompt. Not yet configured or tested.
* **Packaging.** `tools/build-release.sh` still installs the sway-era file
list. The desktop is installed by `tools/lab/install-desktop`, which is the
shape the package needs but is not the package.
* **Parent Call and Live View.** Placed in the architecture, not built. Live
View's pin is KWin's Keep Above, which is already a title-bar button.
* **UI scale.** `classroom-scale` now asks `kscreen-doctor` instead of the
compositor, and 1.25 was applied and read back correctly. The *look* of it
could not be judged: a nested session shrinks its own canvas to the
logical size instead of keeping the display's mode, so everything got
smaller rather than larger. On a real output it does the opposite. Judge
this one on hardware.
* **Network states.** plasma-nm is in the bar and live — it correctly showed
a problem state throughout, because Docker configures the container's
interface before NetworkManager sees it and NM reports limited
connectivity for it. Online, connecting and offline transitions need a
machine whose network NetworkManager actually owns.
## Unresolved risks
In the order I would worry about them.
1. **The DVD player is half-ported.** Its surfaces and its disc detection
work; its window focusing still speaks sway and silently does nothing.
A room that uses DVDs should not take this build until that is finished
and a real disc has been played.
2. **SDDM is configured and untested.** It cannot be exercised in a
container, and it is the thing that starts the session. If the config is
wrong the room boots to nothing. This is the first thing to check on a
VM or a bench machine, and it is why the checklist below starts there.
3. **No real audio hardware has been through the policy.** The decision is
well tested; applying it is not. A wrong `pactl set-card-profile` on
real hardware is a silent room.
4. **Plasma is a much larger surface than sway.** More code, more services,
more that can regress in a Debian point release. The mitigations are
that all of it is supervised, that Debian 13 is frozen apart from
security updates, and that the OTA channel structure already lets a
build be held back. But it is a real trade and it should be named.
5. **Performance is unmeasured on classroom hardware.** Everything here ran
on llvmpipe, which says nothing useful about an old office machine with
Intel graphics. Plasma with compositing is heavier than sway.
6. **Chrome remains a third-party repository.** Unchanged from today, but
still the one part of the stack Debian does not update.
7. **The wallpaper is a stand-in.** The real illustration has not been seen
against this palette.
## Would I trust it on a classroom computer, unattended, for a Sunday?
**For a room that does not use DVDs: yes, after the checks below.** The
reasoning is not that Plasma is nicer. It is that the four failures the
church actually reported are all failures of code we wrote, three of them
are gone because the code is gone, and the fourth — the panel disappearing
— went from *reproducible on the third crash* to *survived eight kills* and
is now a supervised system service rather than a process started by a
compositor and forgotten.
**For a room that uses DVDs: not yet.** Finish the focus port and play a
real disc first.
Before any room:
1. Boot a bench machine from the package and confirm SDDM autologins, and
that killing the session brings it back rather than a shell.
2. Put a television on HDMI, switch it on after the computer, and confirm
sound arrives without touching anything, with the browser already open.
3. Print to Elmer.
4. Play a DVD, if that room uses them.
5. Leave it running from Friday to Sunday.
## Alternate operating systems
None is required. The recommendation stays on Debian 13, which the classroom
already runs and already packages for.
If the church later wants Fedora KDE or Arch compared on real hardware, that
needs a VM this session could not create:
> **Recommended next candidate: none.**
> Debian 13 + Plasma 6.3 was actually tested, in a container, on this branch.
> A disposable VM is needed only for what a container cannot show:
> 4 vCPU, 8 GiB RAM, 60 GiB disk, a real GPU or working virtio-gpu,
> an HDMI-capable audio device, and an optical drive — to test DVD
> playback, audio switching against real hardware, SDDM autologin,
> display hotplug, and performance on classroom-class hardware.
The existing Debian classroom test VM should be kept.
## Checkpoint log
* Branch opened at `aadb1b8`; host recorded as unable to create a VM.
* Lab built; Plasma 6.3.6 running software-rendered at 1920×1080.
* Panel supervision measured, failure reproduced, drop-in written, re-measured.
* Classroom desktop written: colours, layout, window rules, Kiosk, launchers.
* Task states measured; minimise removed as a route.
* Secondary windows, print dialog, notifications, fullscreen, themes, lockdown
all verified on a running session.
* Audio policy written with the failing room as a test.
* Cold starts repeated.
**Next**: SDDM autologin; move the file list in `tools/build-release.sh` to
the Plasma layout and build a `0.1.45~plasma1` package on its own channel;
Plasma display scaling at 1.25 and 1.5; then a real VM for DVD, audio and
performance.