Commit Graph
23 Commits
Author SHA1 Message Date
ekklesiaandClaude Opus 5 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]>
2026-09-20 20:55:09 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-19 22:40:16 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-19 21:29:17 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-13 08:34:37 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-12 19:05:09 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-10 23:06:56 -05:00
ekklesiaandClaude Opus 5 6050144e5d docs: the power button, the maintenance chord, and why long-Escape should change
Records what was measured this pass and, as importantly, what was not.

KGlobalAccel will accept a bare Escape and its component carries both a
pressed and a released signal, so hold-and-cancel is expressible. What
could not be measured here is whether a global shortcut takes the key
away from applications, because key injection cannot reach the nested
session. The mechanism says it does — KWin matches shortcuts in an input
filter ahead of the focused surface — which would cost the classroom
every ordinary use of Escape. That is inference, and it is written down
as inference, with the thirty-second bench test that settles it.

The recommendation is to change the gesture rather than grab the key an
appliance uses most: hold-to-confirm on an on-screen control, which is
ordinary Qt inside our own surface, or a modifier chord like the one the
maintenance terminal just proved. Either keeps the deliberate ceremony
without an input daemon.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 15:24:28 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-10 15:17:55 -05:00
ekklesiaandClaude Opus 5 ed702584d8 docs: refresh the lockdown screenshots against the packaged panel
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 13:10:09 -05:00
ekklesiaandClaude Opus 5 13aac85b87 docs: record Parent Call, the DVD migration and what is left to test
Three things in this window that a container cannot answer, and they are
now the top of the next-steps list: a real key pressed into Parent Call,
a television switched on after the computer, and a disc that plays.

Also brings the comments in the shared libraries up to date. The only
mention of sway left in the tree is the upgrade step that removes the
sway-era login shell from a room being upgraded.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 13:09:42 -05:00
ekklesiaandClaude Opus 5 e8078e37c1 test: one command for every recovery property the room depends on
Run against the lab as the package built it, not a session edited by
hand. The panel came back from eight SIGKILLs a second apart in about
2.2s each time, the compositor in 0.1s, the audio policy from three, and
neither Parent Call nor the browser dying disturbed the panel.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 13:05:18 -05:00
ekklesiaandClaude Opus 5 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]>
2026-09-10 13:00:50 -05:00
ekklesiaandClaude Opus 5 18cf0249dd feat: give Parent Call a mark that says what it does
Three marks were put on a real panel and judged at the size the bar
actually draws them. A bell sat inches from Plasma's own notification
bell and meant something entirely different. A handset with an outgoing
arrow read as a telephone feature. Breeze has people and it has
telephones and the two never meet, so this is drawn in Breeze's language
instead: an adult, a child, and one small speech bubble, on Breeze's
22-unit grid with its ColorScheme-Text class, so it recolours with the
session and needs no second file for the light theme.

Quick Launch carries it. The Icon widget copies the entry into the user's
home so an update could never change it; a task manager reads the entry
live but also shows every running window, which filled the auxiliary
corner with the lesson's tasks.

One defect found on the way, and it affected the Wonder Ink icon that is
already shipping: gdk-pixbuf recognises an SVG by sniffing the first
bytes, and a licence-length comment above the opening tag pushes it out
of that window. Every GTK program reported "couldn't recognize the image
file format" while Qt rendered the same file happily. The comments live
inside <svg> now.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 12:47:58 -05:00
ekklesiaandClaude Opus 5 323989ac61 fix: let an update change the Parent Call button
Two findings, both from watching the bar refuse to change.

The Icon widget copies the desktop entry into the user's home and uses
the copy. A classroom that had already run kept the old icon and the old
command for ever: the package no longer contained a bell and the bar kept
showing one. The button is a task manager holding a single launcher now,
which resolves /usr/share/applications live — the place an OTA update
writes to — and costs nothing, because the classroom already runs one for
the applications.

NoDisplay=true then hid the entry from the very index that resolves
launchers, and the bar drew a blank page. There is no application menu in
this classroom for the entry to clutter, so it is indexable now.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 12:38:27 -05:00
ekklesiaandClaude Opus 5 7790003c03 feat: let a teacher ask the sanctuary to call a child's parent
The coordinator already existed on the control computer and its contract
is frozen, so this is the classroom half of a contract that was read from
its README and source rather than guessed: four fields out, the status
topic subscribed before anything is published, and every word the teacher
reads taken from the coordinator's own message field.

It is a separate process, not a Plasma applet. A broker library, a
network timeout and a text entry have no business inside the only way a
teacher has of getting anywhere; if this dies the bar is untouched.

The panel mark is Breeze's call-outgoing. The old one was a bell, sitting
two inches from Plasma's notification bell in the same bar and meaning
something entirely different.

Two things it will not do: claim a call was displayed, and let a teacher
type the room. The machine already knows which room it is.

Sixty tests cover the contract without a broker. The live path was proven
against the real coordinator running in the lab, on a loopback broker,
with ProPresenter mocked and the church's networks rejected from inside
the container first.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 10:37:06 -05:00
ekklesiaandClaude Opus 5 828362b4f4 docs: index the screenshots, and drop the ones that showed nothing
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 09:13:13 -05:00
ekklesiaandClaude Opus 5 ca33669cf7 docs: close the checkpoint with the from-package reproduction
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 09:12:28 -05:00
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
ekklesiaandClaude Opus 5 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]>
2026-09-10 09:06:53 -05:00
ekklesiaandClaude Opus 5 6e4786f448 build: package the desktop instead of the shell
The install list loses the dock, the window helper, the Chrome window
arbiter, the notification wrapper, the compositor and bar configs and the
GTK icon and stylesheet machinery, and gains a look and feel package, a
colour scheme, window rules, Kiosk restrictions and two supervised user
services. Dependencies follow: Plasma and SDDM in, sway, waybar, mako and
gtk-layer-shell out.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 08:59:56 -05:00
ekklesiaandClaude Opus 5 d87990f0ac docs: write up the classroom v2 result
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 08:54:41 -05:00
ekklesiaandClaude Opus 5 2ff9d47be9 feat: decide the classroom's audio output from what is plugged in
The room that prompted this had a television on HDMI with working PCM
audio and nothing in the analog jack, and WirePlumber chose analog. A
teacher who corrected it by hand then had to restart the browser before
any sound came out.

The policy is a pure function so it can be tested against that room
rather than against a machine: an unavailable port is not a candidate,
an analog port merely existing is not evidence of a cable, and an
explicit AUDIO_OUTPUT overrides the lot. Twelve cases, the real room
among them.

The daemon follows PipeWire's own event stream rather than polling, and
moves streams that are already playing, which is the part that stops a
teacher having to close and reopen the browser.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 08:47:28 -05:00
ekklesiaandClaude Opus 5 87b55761c6 feat: give the classroom a desktop instead of a shell
The whole appliance UI is now configuration for a desktop Debian already
ships: a Plasma 6.3 session with a colour scheme, a look and feel package
that builds the bar, window rules, and KDE's own Kiosk restrictions.

Two things were measured rather than assumed, and both changed a decision.

Plasma restarts a crashed panel three times a minute and then gives up,
which is the failure a classroom has already had. Four lines of systemd
drop-in make it unconditional; the panel then survived eight SIGKILLs a
second apart, back each time in 2.4s.

Plasma draws a minimised task and a background task identically, so the
five states the room needs cannot all be shown. Rather than write a task
manager, the classroom removes minimise: clicking a launcher always means
show me this.

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