6946bae455fbcd8937eeb189546e3bfb34796b03
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1d9da3bca0 |
test: rehearse the stable rooms' upgrade on every canary, and list Sunday's checks
update_canary.sh now also validates the exact package it is about to publish as an upgrade from whatever stable names — 0.2.2 today, several releases behind the laptop's canary. A package that upgrades the laptop and breaks the desktops has to fail before it is published. The lab print check runs in the Plasma lab as well as a plain container: its rig lived in /tmp, which is a tmpfs there that docker cp cannot write into, and its inline-PDF server shared a port with another test server. docs/testing.md has the physical checks for 0.2.6.1 — paper through Elmer, the real keyboard's zoom keys, the machine's graphics through --diagnose, sign-out with a real account — before stable moves. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
88ea3e86fd |
feat: measure why lessons load slowly, and give a technician the same tool
Measured before changing anything, and nothing about loading changed, because the evidence does not point at Wonder Ink. Same lab, same page, timed by each engine until the sign-in form exists: Wonder Ink 2.86-3.00 s cold and 2.20-2.49 s warm, Chrome 2.79-3.48 s and 1.96-2.15 s. The site is the weight — 119-160 requests and 3.2 MB for a sign-in form, and its console reports loading the Google Maps API twice. Memory about 610 MB for one tab, 115 MB per further tab; idle CPU about 1%. The lab draws in software for both engines, so it cannot show the one likely cause it has no hardware for: QtWebEngine failing to use a classroom's GPU where Chrome succeeds. classroom-wonderink --diagnose, run from the maintenance terminal in the teacher's session, prints the engine versions, the scale, the WebGL renderer (a driver, or software), and the sign-in page's time from a cold cache, in a throwaway profile beside any running Wonder Ink. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
1cb9fd35d2 |
feat: Wonder Ink looks like the classroom, and its preview shows the page
The tab strip is drawn from the classroom's own palette, the one the panel, Parent Call and Presentations read: larger titles, an accent under the active tab, worksheets marked with a document icon. Its one corner widget is the lesson's size and Print — not a new-tab button. With one tab the strip stays hidden, as before; a change of size shows a moment's badge instead, and the right-click menu and keys still work. The window is named "Lesson — Wonder Ink" and carries its own icon. The print preview showed "Page 1 could not be shown" in the real session while printing correctly: poppler 25.03 does not write to stdout when asked for "-" — it writes a file called "-.png" into the current directory, which for Wonder Ink is the teacher's home. Pages are now rendered into a directory of their own, and the lab print check fails any preview that does not draw its page. The teacher's manual has lesson size and printing. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
48b3097c43 |
feat: a lesson in Wonder Ink is the size the browser shows it, and zooms
Teachers found lessons in Wonder Ink too small next to the ordinary browser. Measured by asking each page for devicePixelRatio: Wonder Ink follows the display scale exactly (dpr 1.5 at 150%), and Chrome is scaled TWICE (dpr 2.25) — under Plasma it follows the compositor itself and classroom-browser also forces UI_SCALE, a flag from the sway session. The browser is left as it is. Wonder Ink's pages start at zoom = UI_SCALE, the room's own number (1.0 at 100%), and measured afterwards match the browser: dpr 2.25 and 853 px against 2.25 and 854. The window is not zoomed; Qt scales it once, correctly. Ctrl+Plus/=/keypad, Ctrl+Minus, Ctrl+0, Ctrl+wheel and the right-click menu set one level for every tab, remembered as LEVEL= in its own file, relative to the room. Two actions on one key are ambiguous to Qt and it then does neither — Ctrl+Minus did nothing until each command became one action with all its keys, which also fixes Ctrl+Shift+Tab. The engine profile is built by one function, which the lab's checks now use: a check with its own profile showed every worksheet blank. The lab fences 10.10.0.0/16 as well: the development host reaches the real Elmer over Tailscale, and the lab provisions a queue pointing at it. Four lab checks written for older code are corrected; one had failed on every run since 0.2.3. tools/lab/wonderink-zoom-check: 18 checks. Lab: converged 120, print 39, worksheets 24, all passing. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
10433b850d |
fix: Wonder Ink prints without crashing, and prints worksheets at all
Little Waders, 0.2.2: SIGSEGV in QPrinter::resolution() every time a teacher confirmed a print job. QWebEngineView.print() keeps the raw QPrinter pointer and returns; Qt's didPrintPage() reads printer->resolution() once Chromium has rendered the page, and by then PyQt had deleted the printer, which print_view() held only as a local. Reproduced in the lab with the 0.2.2 code, same backtrace, no job. Keeping the printer alive fixed the crash and was measured, and Qt's path was still not a print path: a worksheet PDF tab printed as one blank page, a lesson went as 24.6 MB of 300 dpi pictures with no text, and a queue refusing jobs was reported as printed. Print is now: make a PDF (a worksheet already is one; a lesson page is printToPdf'd onto the printer's own paper; an inline PDF is downloaded and opened as a worksheet), show it in a preview with printer, pages, copies and colour, and hand it to CUPS with lp — Chrome's route in the same rooms. Nothing asynchronous holds a printer. Black and white uses the driver's ColorModel=Grayscale, because Elmer's generic PCL 6 driver ignores print-color-mode. An exception escaping any slot no longer aborts Wonder Ink: PyQt6's default is to kill the process, and it is now logged instead. tests/test-classroom-printing.sh: 50 checks. tools/lab/wonderink-print- check: 39 checks over 13 scenarios in the lab, against a queue built from Elmer's PPD, judged by what CUPS received. Not yet on paper. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
690ef95aff |
feat: four-part versions, and a published version is never used again
0.2.6.1 is a maintenance release of 0.2.6. The build refused four parts, update_canary.sh's automatic bump could not add one to "6.1", and neither publish script would have noticed a version being published twice — which is what happened to 0.2.6 this morning: it was rebuilt with the Presentations hardening and uploaded over the first 0.2.6, and a room that had installed the first can never be offered the second. One release library now holds the version pattern (three or four parts, no leading zeros, no .0 maintenance), orders by dpkg --compare-versions, refuses a version that has ever been published or is not newer than all of them, uploads with --ignore-existing and checks the server's checksum, writes a manifest only for a package the server holds, and reads the channel back over HTTPS afterwards. update_canary.sh takes the version explicitly; update_stable.sh refuses to go backwards. The classroom updater refuses a manifest whose version, package name or checksum is malformed, and refuses a download whose own control file is not the version the manifest promised, so a room can never loop on reinstalling and rebooting. tests/test-release-versions.sh: 87 checks against a stand-in server and real dpkg. The Trixie gate now also rehearses a stable room: 0.2.2 to an interim 0.2.6.1 upgraded with settings, channel and Presentations=false intact, and the installed updater answering correctly. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
6f2d169355 | fix: harden classroom presentations | ||
|
|
9c864a1e44 |
test: the release gate is bounded, which is not the same as a number
These pinned "720s" and "candidate-install 360" as literal strings. When 0.2.6 raised those budgets for LibreOffice, the install check failed — and the outer-deadline check went on passing, because the comment explaining the change still contained "720s". A test a comment can satisfy is not testing anything. What matters is that nothing in the gate runs unbounded and that each budget is long enough to install a desktop, so that is what it now reads. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
52301084a5 |
feat: an external-only display mode, chosen rather than inferred
The women's Bible study laptop is a fixed appliance: AC power, battery out, lid shut, one television on HDMI. Its own panel should be off. DISPLAY_MODE=external-only says so, and it is a setting rather than something inferred from having a lid, because another laptop may want its own screen or both. Nothing reads DEVICE_TYPE to decide it. The built-in panel is identified by kscreen's output TYPE — 7 is Panel — rather than by connector name. Matching "eDP-1" would be matching one machine's spelling: eDP, LVDS and DSI are all internal, numbered differently per driver and renamed between kernel versions. The names are kept only as a fallback for a driver that reports no type, which some virtual devices do. One rule overrides the setting: never leave the machine with no screen. A laptop asked for external-only whose television is off keeps its own panel and says so in the journal. A machine nobody can see is one nobody can fix standing in front of it, and it corrects itself the next time this runs with the set on. Only the outputs that are not already right are touched, so a television power cycle is not a stream of mode sets. Applied at login from an autostart entry named to sort before the scale one, so the scale lands on the displays this leaves enabled. The lid itself needed no new code — logind ignores every lid event, PowerDevil's lidAction is 0 in all three profiles with triggerLidActionWhenExternalMonitorPresent=false, the profiles are immutable and the classroom account cannot reach suspend through polkit. That was all already true for towers. What was missing was a test saying so, and there is one now, because it is the property the laptop depends on and nothing would have noticed it regressing. KNOWN LIMITATION, and it is written into docs/testing.md rather than left to be discovered: the mode is applied at login, not continuously. If KWin re-enables the internal panel when the television is power cycled, it stays enabled until the next login. The lab has no lid and no HDMI hotplug, so this cannot be settled here; the physical procedure says to check it specifically, because the answer decides whether the follow-up needs an event-driven applier like the audio policy's. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
886f8a70d2 |
feat: one download address per channel that never changes
https://updates.riverofglorymn.org/stable/latest.deb https://updates.riverofglorymn.org/canary/latest.deb A symlink beside each channel's manifest, pointing at the versioned package the manifest names. Deliberately not a second record of which version is current — the manifest stays the only answer to that, and the publish scripts derive the pointer from it rather than from anything they know themselves. The versioned files are never rewritten. A release uploads its package, then the manifest, then swaps the pointer by rename. Atomic, so nothing downloads a half-written pointer; and last, so the pointer can lag the manifest by a moment and can never lead it. A pointer that led the manifest would offer a package the channel had not published. Both latest.deb and manifest.json are served no-store on the update server. Cloudflare sits in front of it and caches .deb by extension, so without that header a room could be handed the previous release from an edge for hours. The versioned packages keep no such header because they are immutable and worth caching. The server change is one matcher in the Caddyfile's updates block, and it took three attempts to land for a reason worth recording: the Caddyfile is a FILE bind mount, so `mv` over it replaces the inode and the container goes on serving the old one. Two apparently successful reloads changed nothing. It is written through the mount now, and the host copy is identical so the next container restart is a no-op. Caddy was reloaded, never restarted; the church's other sites were not interrupted. Installing from a channel URL still does not move a machine onto that channel. CHANNEL in device.conf decides which manifest the updater follows, and the postinst only appends settings that are missing, so fetching the canary package by hand to repair a machine leaves it on stable. Verified end to end against the live server: both URLs deliver bytes whose sha256 matches their channel's manifest, both carry the no-store header, and the versioned package does not. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7f4a122900 |
feat: one setting per application, and never the shape of the machine
A laptop is going in for the women's Bible study, and the obvious way to
build that — a laptop profile that turns off the children's church
applications — is the wrong way. A laptop may well take children's church,
and a tower may well be a Bible study. Hardware cannot be allowed to
decide purpose, so every optional application gets a setting of its own:
WONDERINK_ENABLED already existed; behaviour unchanged
DVD_ENABLED new
PARENT_CALL_ENABLED new
and DEVICE_TYPE says only what the hardware IS. classroom-desktop-layout
never reads it, and there is a test that says so, because this is the rule
that will be under pressure the first time somebody adds a device.
All three default to true, so an existing classroom that has never heard
of any of them comes up exactly as it did before. The settings are
appended to device.conf by the same mechanism every other setting has
used, so an upgrade gains them without losing a byte an administrator
wrote.
The disc player is the one place hardware still votes, and it is a veto:
DVD_ENABLED can take the player away from a machine that has a drive,
nothing can put it on a machine that has none. A button that cannot work
is worse than no button.
The layout's launcher markers are replaced by a feature block of three
plain booleans. The old mechanism commented a line out of an array, which
worked for launchers and could not express Parent Call, because that is a
whole widget rather than one entry. Three booleans express both, and the
launcher list is now built from them in one fixed order, so a disabled
application closes the row up rather than leaving a gap, and the same
enabled set produces the same bar on every machine in the church.
Also here, because it is the bar and not a feature: sortingStrategy=0.
Plasma's default is 1, "manually", and the task manager accepts a
reordering drop only in that mode — MouseHandler.qml tests
tasksModel.sortMode === SortManual before it will move anything. The
classroom never wrote the key, so it had the default, and any teacher
could drag the launchers into a different order. This is the applet's own
lever refusing the drop rather than something of ours putting the bar back
afterwards.
The test now runs the layout the package actually ships, in node, with
Plasma's API stubbed, and asserts the launcher string that comes out.
Asserting against a copy of the file's text in the test was how the old
one worked, and a copy is a thing that drifts.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
5f2a9098c1 |
fix: a worksheet a lesson downloads comes back as a tab
Some Wonder Ink lesson links are a download rather than a page: the server sends the PDF as an attachment, so there is nothing for a tab to show and the file lands on disk. In this room that is the same as it not arriving. There is no file manager, no application launcher and no downloads bar, on purpose, and a teacher is not going to open a maintenance terminal to print a Christmas tree. 0.2.1 downloaded the worksheet, said so in a notification, and left it somewhere unreachable. Found on the canary machine with a real lesson. So a download that completes, and really is a PDF, opens as another tab. Rendering is the engine's own viewer — the same PDFium Chrome uses — so there is no second rendering library and no separate application. That was verified against a completed local file rather than assumed from the remote case: PdfViewerEnabled was already on, and a local file: URL is a different question. WHAT IS REFUSED. A completed download is not a usable worksheet. HTML that a server called .pdf, an empty file, an unreadable one: all refused on the first five bytes rather than on the name, because the alternative is a blank tab that says nothing. An interrupted download says it did not finish and to try again — and that needed stateChanged rather than isFinishedChanged, which is not emitted for an interrupted download at all. Connected to the obvious signal, this said nothing whatsoever about a worksheet that failed to arrive, which is the one case a teacher most needs told about. Measured. WHICH LOCAL FILES. Exactly the ones the window opened itself. Opening a worksheet adds one resolved path to a list, and the page's acceptNavigationRequest refuses every file: URL that is not on it. Chromium already refuses to let a remote page reach file: — verified with a link, a location.href, and a link to a worksheet that had been downloaded but not opened by the shell, all refused — so this is the second lock rather than the only one. It is here because the first one is somebody else's. Two Qt defaults are turned off beside it: LocalContentCanAccessFileUrls, which is on by default and would let a local document read other local documents, and its remote counterpart. Print to File is removed from the print dialog, because choosing it enables a path with a "..." button, which is a file chooser, in a room whose whole design is that there is no file chooser. The PDF viewer's own menus were audited: two-page view, annotations, present, properties, and nothing that opens or saves anywhere a teacher chooses. AND THE BROWSER IS NOT INVOLVED. The handler is connected to Wonder Ink's own profile, so it only ever sees downloads that started in a Wonder Ink tab. Nothing inspects a filename, nothing watches a directory, and Chrome — a separate application with a separate profile — is untouched: classroom-browser and the Chrome policies are byte-identical to stable 0.2.0. Worksheets go to ~/Downloads/Wonder Ink, which is tidiness and mostly the security argument: it is the only place one can come from. Only PDFs. Anything else downloads, says so, and is still unreachable; that limitation and two small follow-ups are written down rather than built, and nothing downloaded is ever executed. Measured from the package on a rebooted machine, and again on one upgraded from the published 0.2.1: the click-to-tab workflow end to end, a name with spaces and brackets, multi-page navigation, printing to Elmer, closing the worksheet without closing the lesson, and the four refusals. 120 convergence checks and 24 worksheet checks pass on both. Cookies are still session-only, the Present-view autoplay fix still works, fullscreen still comes back maximised, and there is still no address bar and no new-tab button. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
de5025ad22 |
feat: give Wonder Ink tabs, and let a device say it has no Wonder Ink
Teachers prepare a lesson across several tabs — the lesson, its video,
the activity — and app mode is the one thing that cannot give them that.
Chrome cannot be asked for "tabs but no omnibox". App mode has no tabs; a
normal window has an address bar and no policy, flag or extension removes
it; tabbed web apps need a manifest on a site that is not ours and push
off-origin links out into a normal browser window, which is the whole of
what this design exists to prevent. So the tab strip is ours, and the
engine is Chromium either way.
QtWebEngine, from Debian, measured before it was chosen rather than
after. The usual and correct objection is codecs: on Debian 13 it reports
H.264, AAC, MP3, VP8 and VP9 all playable, MediaSource supports avc1,
mp4a and vp9, and a 720p H.264 file decoded and played to the end in the
lab. app.wonderink.org loads, and its sign-in is an ordinary email and
password form rather than a Google flow that would mind an embedded
browser. The user agent has the QtWebEngine token stripped so a site
gating on it sees the Chromium this actually is, and Widevine is handed
over from Chrome's own bundle when the machine has Chrome, which a
classroom always does.
[ Lesson ] [ Video ] [ Activity ]
The strip appears only above one tab, so an ordinary lesson looks exactly
like the application it replaces. A tab can only come from the lesson —
target=_blank, window.open, a middle click, a Ctrl+click — and there is
no address bar, no search and no button that makes a blank tab. No
allowlist of domains either, on purpose: a list would have to be right
about every video service a lesson might ever embed.
Three bugs the lab caught and this fixes. A lesson calling window.print()
did nothing at all, which is worse than no print button because a teacher
concludes the printer is broken; printRequested is now bound to the page
that asked. Leaving fullscreen left the window 200 pixels wide in the
corner — a window that has only ever been maximised has no ordinary
geometry to return to — so the fullscreen bit is cleared rather than
maximised being re-requested. And the Present view video failure written
up in docs/wonder-ink.md is acted on at last: that investigation named
the autoplay gesture chain as the likeliest cause and declined to fix it,
because in Chrome the only lever governed the ordinary browser as well.
Here it does not — this profile is Wonder Ink and nothing else — so a
lesson may start its own video and no other page in the room may.
Reproduced both ways in the lab with an unmuted video started by a script
with no gesture: NotAllowedError at Chrome's default, playing as shipped.
That is not proof Present view is fixed; the lab has no Wonder Ink
account, and candidates 2 and 3 in that document are still open.
Measured on a rebooted machine, from the package: tabs open from a link
and from window.open, switch, and close; closing one leaves the others;
closing the last closes Wonder Ink; H.264 video plays and fills the
display in fullscreen and comes back maximised with its tabs; the print
dialog offers the room's own Elmer queue; a download lands in ~/Downloads;
back and forward work from a right-click menu that is built rather than
filtered; pressing the panel button twice raises one window rather than
starting a second on the same profile; KWin reports the window as
ekklesia-wonderink, which is one panel button and not two.
Separate from the browser as before, in its own profile, with cookies
that live only as long as the window — what the Chrome policy did for
wonderink.org, and the reason closing Wonder Ink ends a teacher's
session. Chrome's old profile directory is left untouched beside it, and
the browser's own policy is not edited by any of this.
Wonder Ink is now optional per device, through WONDERINK_ENABLED in
device.conf, because one future device is a laptop that will not have it.
It rides the mechanism the disc player already uses: one marked line in
the packaged layout, edited by classroom-desktop-layout. False takes the
launcher off the panel and makes the command refuse to start; the
browser, Parent Call and the disc player are untouched and the panel
stays balanced. Absent means yes, so every existing room is unchanged.
One scale, still. Chrome has to be handed UI_SCALE because it was
measured not to follow the compositor; Qt does follow it — devicePixelRatio
2.0 at UI_SCALE=2.0, measured — so Wonder Ink is deliberately not told,
or the room would get it twice.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
8db5e8dd9f |
release: the Plasma classroom becomes stable 0.2.0
0.1.50 was taken to the church and the things a container cannot answer were answered on the hardware: boot, autologin, the panel after a reboot, panel and browser scaling, Wonder Ink, Parent Call opening in the corner and reaching the sanctuary with a typed name, readable and silent notifications, HDMI audio and the volume keys. The promotion is a fast-forward. The experiment was cut from the published stable source and never diverged from it, so master reaches this work without a merge and there is one history. One thing changed for the release. The channel rules turn around: classroom-v2 and hyprland are the obsolete channels now and a room on either comes home to stable, while canary — where the next development release goes — and any channel somebody named by hand stay where they were put. A fresh installation has always defaulted to stable. Nothing else moved. This is the 0.1.50 that was verified, with a version number and a channel rule. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
ba8768cee5 |
fix: a room that was set up, a card where its button is, and larger cards
Parent Call told rooms they had never been set up. Nothing about the rooms was wrong. Read from the church broker: …/coordinator and …/pending are retained and …/mode is absent, and the coordinator's own source says why — it publishes the retained mode only in answer to an operator writing to …/mode/set, and nobody ever has. Every classroom waited a second and a half for a message that will never exist. A missing mode is now read as the coordinator's own default rather than as an unconfigured room: the room offers the call and lets the sanctuary end answer, because that is the end that knows. A mode that really is off still refuses, in the coordinator's words, and a broker that is missing or unreachable still says which of those it is. Proved against a rig broker made to match the church's exactly, with a call landing on the sanctuary mock. The card is anchored to the bottom-left corner instead of centred, by layer shell, which is what the disc player's cover has used under KWin for months. Anchoring rather than positioning is what makes it right at 1080p and at 4K without a coordinate anywhere. It falls back to the centred card if the library is missing. Notifications are sized for a room. Every Plasma measurement is a grid unit and a grid unit is the height of the general font, so the font is the lever — 15 point instead of Breeze's 10, which takes the card from about 350x110 to about 410x145 at 100% and doubles again at 200%. Text, icon, padding and buttons move together. The chime stays off. No shell package: that path cost the panel and is not coming back. And the product name is gone from everything a teacher sees. The notification card is from Classroom, the disc player's from DVD. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
bd55c5abd9 |
fix: let the migration hand device.conf to the one place defaults live
classroom-migrate knew how to remove a setting, move a channel and point a room at the broker, and knew nothing about which settings the classroom has gained since a room was installed. That list is in classroom_config.py and it stays there: the migration now hands the file to ensure_device_config when its own work is done, rather than growing a second copy of it. It matters for the command rather than the package. postinst already called the shared helper before running the migration, so an install converged; a technician running `sudo classroom-migrate` by hand on an old machine got a file that was still short of LIVE_ENABLED, AUDIO_OUTPUT and UI_SCALE. Both paths end in the same place now, and the release gate runs the command by hand on an already-converged machine to prove it. The validator's own expectation was the release-blocking failure, and it was not what it looked like: it stops at CHANNEL=canary, before it ever reaches the LIVE_ENABLED count. canary is the sway line, and moving a migrated room off it is deliberate — a Plasma room left there is handed the old desktop back by the next release published to it. The fixture now expects the move, checks that every setting this release has arrived exactly once, and checks it again after a reinstall. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
34fd02e08f |
feat: give every room the broker, one scale, and its own panel back
Little Waders took the Plasma build and reported what a container cannot. A disc player on a machine with no optical drive, a clock nobody at the back of the room could read, a browser at half the size of the panel around it, and Parent Call with no broker to call. The broker is the same for every classroom, so it is a default now and not something to fill in room by room. classroom-migrate brings an existing room to the same place without touching its name, its username or its password, and Parent Call distinguishes a broker it cannot reach from one that was never set up: those send somebody to look at completely different things. The disc player's detection was right and ran too late — alongside the session rather than before it, and the panel is built from the layout the moment a session starts. It runs before the display manager now, and again from udev when a drive is plugged in. UI_SCALE had a ceiling of 1.5, which cannot express a 4K television; a clock pinned at 15 points, which is the same size at every scale; and a browser that was never told the number at all. All three are fixed and the room doubles together at 200%. This also brings 0.1.47's work into the repository, where it had only ever existed in a build directory under /tmp: the hidden display dialog, its authenticated writer, the polkit action, and the church's own wallpaper. One piece of it is deliberately left out. The custom volume display needed a shell package of our own, and with one selected plasmashell builds no panel at all — measured twice, with that build's own files, on a classroom that deletes its panel configuration at every login by design. The lab connected to the real church broker the moment the package defaulted to it. Nothing was left behind, which was checked, but the guard that should have stopped it had never worked: it used iptables, which the lab image does not have, and probed with /dev/tcp under dash, where that always fails. Both reported success. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
926668dc9b |
fix: make an existing classroom converge into the Plasma appliance
A real room upgraded to 0.1.45 and came up as stock Debian Plasma with the old Waybar across the top, mako still catching notifications, and a lock screen on an account that has no password. The cause was one line, and it was in the lab: the harness handed the session under test XDG_CONFIG_DIRS by hand, so every result it had ever produced was a result about a machine somebody had already configured. On a machine SDDM starts the session through PAM, PAM does not read /etc/environment.d, and Debian's own plasma-workspace env.sh then set the variable from scratch — so the classroom's settings directory was not in the cascade at all. No layout, no lock policy, no Kiosk restrictions. The settings now arrive the way Plasma delivers them, through a file in /etc/xdg/plasma-workspace/env. Around that: the look and feel is immutable on its own key so a stale profile cannot answer for it; the palette is composed into the package, because naming a colour scheme does not produce one and the dark bar had been coming from a technician's hand; the desktop reset converges another desktop's appearance state and keeps the room's own; and classroom-migrate stops, deactivates and masks the old shell, including the D-Bus activation that brought mako back while the panel was down — found only by killing the panel on a migrated machine. device.conf loses COMPOSITOR and comes off the hyprland channel, which would otherwise have handed the room the experiment back. The lab stops flattering the package: it restarts the machine, lets SDDM actually autologin, and asks a fresh install and a machine upgraded from the Hyprland package the same sixty questions. Both answer identically, twice, and the panel still comes back from eight SIGKILLs. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
30a924d945 |
feat: the power button does nothing, and one chord opens a terminal
Two layers for the button, because one is not enough: while a session is running PowerDevil holds logind's handle-power-key inhibitor and acts on the button itself, so a logind drop-in alone would be overruled. logind covers the machine before the session starts, after it ends, and if PowerDevil is not running. Verified: after the drop-in is installed and logind reloaded, it reports ignore for the power, suspend, hibernate and lid keys, and the running session was not disturbed by the reload. Holding the button still cuts the power. That is firmware, below any operating system, and it is deliberately left alone as the only recovery a person standing at a wedged machine has. The maintenance terminal is Meta+Ctrl+Alt+Shift+K, declared in the package rather than seeded into a teacher's configuration, where an update could not reach it. KGlobalAccel read it and registered the component; invoking it opened a working shell. It needed a wrapper. The classroom's Kiosk policy sets shell_access=false and Konsole honours it — measured: with the classroom configuration in its cascade Konsole starts and says "Program to run not set." The restriction is worth keeping everywhere else, so it is lifted for this one process by leaving the classroom's configuration directory out of its cascade. It grants no privilege: the classroom user has no sudo rule, and that restriction was always a barrier in the interface rather than a privilege boundary. Konsole's own entry is shadowed and hidden from /usr/local/share, so nothing is patched and Konsole's package stays maintainable. Nothing is pinned to the panel; a task button appears only while a terminal is open, and closing it returns the bar to three launchers. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
d2d30d6b0c |
feat: take the last of sway out of the classroom
The DVD player was the only thing still speaking a compositor the room no longer runs. It asked sway's tree whether VLC's window had really gone fullscreen, because VLC reports playing while its window is still the black startup one the loading cover exists to hide. VLC's own status was measured as a replacement and is not one: it reports fullscreen true in the same 250ms poll as playing, because the flag is the option it was started with and not a report about its window. So the cover is held for a floor and capped by the deadline that was already there, and the compositor is not asked anything. Focus-or-launch went the same way. The session already takes an exclusive lock, so a second one cannot start, and bringing an existing one to the front is the panel's job. classroom-window, classroom_sway and classroom_chrome are gone with it — the last of them was reachable from nothing at all. DVD also moves out of the status row, where it sat beside the clock as though a disc were a machine reading. It is an application: it is in the row with Wonder Ink and the browser, and classroom-provision removes it from the layout on a machine with no optical drive, which retires DVD_ENABLED. A computer that moves between rooms takes its drive with it; nobody remembers to edit a file when it does. The panel is rebuilt from the package at every login now. Plasma keeps whatever panel it first built, so an update could change the bar in the package and never change it in a room — measured, with an icon that outlived the file it came from. Editing the live panel configuration was tried instead and destroyed the panel, because the shell owns that file. Removing it before the shell starts is safe and makes the desktop stateless. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
f7d5aecd6c |
build: make the classroom desktop something a .deb installs
Building the package found two things reading the tree did not. dpkg refused it outright: plasma-workspace already owns /etc/xdg/plasmanotifyrc and friends, and two packages cannot own one path. The room's settings move to a directory of their own with /etc/environment.d putting it in front of /etc/xdg, which is KConfig's own cascade rather than a file fight. The postinst then failed on a chmod for a command that no longer exists. It now walks what the package installed instead of naming files, removes the login shell that used to start sway on tty1, and enables SDDM and the audio service. Verified by installing the built package on a fresh machine and starting the session from it: restrictions in force, palette applied, audio service enabled and running, panel supervision unconditional. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
4c3f19d599 |
build: keep the DVD and MQTT helpers, and count the deletion honestly
Building the package showed what reading the tree did not: classroom-dvd still calls classroom-window, classroom-mqtt still calls classroom-notify, and the DVD overlay still imports the icon and stylesheet helpers the dock shared. They go back in, and the write-up now counts what was actually removed rather than what could be. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
f65c814eff |
fix: put the popup close button in the dock, and clear the printer location
Three things, none of them redesigns. The close affordance for a secondary Chrome window was a separate layer-shell surface anchored to that window's corner, repositioned by a five-per-second poll. Any position change tore the surface down and built another, so on real hardware it chased the window and flickered. Removed entirely: the GTK button process, the geometry maths that placed it, the --close-button mode that spawned it, and the poll loop that drove it. The window helper no longer imports GTK at all. The behaviour underneath it was right and is kept. The first Chrome top-level is the classroom browser, tiled and borderless; later ones float centred at about three quarters of the output. Creation order is the only thing that can tell them apart — confirmed on a real session, where every Chrome top-level shares app_id, shell, PID and a null transient_for, and sway exposes no window properties for Wayland toplevels at all. Titles are still not used. It now follows sway's own window events instead of polling, so dragging a popup costs nothing. Close moved into the dock, which was already on screen and does not move, so it cannot chase anything. It appears only while a secondary window is focused and runs kill on that one container. The helper publishes which window that is; the dock watches the file rather than polling. A stock sway title bar has no close button, so restoring the border would not have solved this either. Two bugs found by looking rather than by testing state. Chrome renegotiates the size of a popup opened with explicit dimensions after the resize returns, which left windows centred for a width they no longer had; placement now settles and repeats once, at creation only, so dragging is still free. And the dock's Close button came up empty because show_all does not recurse into a no-show-all widget. The printer's Location is now blank. CUPS prints it under the queue name in every dialog, so "Ekklesia Classroom" was a second line the teacher had to read past; the identity is one word. The already-configured check includes Location, so a machine set up before this is corrected rather than left as close enough. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7f66228b9e | Release 0.1.37 deployment prep |