docs: one manual, the pages a device has and a printable copy of all of them
Documentation becomes part of the release rather than a job afterwards.
docs/manual is now the only place teacher instructions are written, and
tools/build-manual produces both versions from it:
classroom-manual.html installed on every machine, showing only the
features THAT device has
classroom-manual.pdf the universal printed manual, every feature,
with a conditional sentence above each
optional page so it stays true in a room that
does not have them all
They cannot drift apart because neither is written by hand, and
build-release.sh builds and checks them on every release: a page that
declares a feature with no printed-if fails the build, because that is
exactly how the printed manual comes to state an optional feature as
though every room had it. So does a page naming a feature device.conf
does not have, and an optional feature with no page at all.
WHAT REACHES THE CLASSROOM ACCOUNT. Only a list of setting NAMES, and
only ones the package already defines as optional applications.
classroom-desktop-layout — already the one place that decides what a
device offers — writes them to /run/ekklesia-classroom/features.json,
and classroom-manual filters that against FEATURES before using it. No
values from device.conf, no broker password, no addresses. With no
manifest at all every page is shown, which is the printed manual and is
never wrong, only less specific.
Help is a Quick Launch button beside Parent Call, deliberately not in
the row with the applications: putting it there would move buttons
teachers reach for without looking. Every room gets it, because a
teacher who needs the manual is by definition one who does not know
where to look.
The PDF is written directly rather than converted. A manual is
headings, paragraphs, bullets, steps and one table; writing that with
the standard fonts is a couple of hundred lines and no dependency, so a
release can be built on any machine with Python. Pictures are embedded
in the digital manual as data URIs so it stays one file that works
wherever it is copied and with the network unplugged; the printed
manual names them instead, which is the one thing this builder does not
do and is noted where somebody would look for it.
Two defects found building it, both of which hung or corrupted the
output rather than failing: a paragraph opening with a bold word
matched the bullet guard but not the bullet rule, so nothing consumed
it and the build spun; and a list item wrapped across two source lines
ended the list, so a three-step procedure printed as three steps all
numbered 1.
Also adds the 0.2.6 physical checklist to docs/testing.md and the
release validator's checks for the Wi-Fi regression, the conversion
pipeline on a real Debian, the fonts, the manual and the dock order.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
55f4c183c7
commit
f2942a2946
@@ -143,6 +143,181 @@ grep -q "classroom-wifi --check" /usr/local/bin/classroom-migrate
|
||||
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 -c \
|
||||
'import classroom_network; classroom_network.parse("")'
|
||||
|
||||
# THE 0.2.5 WI-FI REGRESSION, ASSERTED ON A REAL INSTALL.
|
||||
#
|
||||
# The prayer-room laptop's network is hidden and its name has a space in
|
||||
# it. The migration dropped the hidden flag, so the connection it built
|
||||
# could never associate. Checked against the module the package actually
|
||||
# installed, not the one in a checkout.
|
||||
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 <<'WIFI_CHECK'
|
||||
import classroom_network as n
|
||||
|
||||
stanza = n.wireless(n.parse(
|
||||
'allow-hotplug wlp2s0\n'
|
||||
'iface wlp2s0 inet dhcp\n'
|
||||
'\twpa-ssid "ROGC Classroom"\n'
|
||||
'\twpa-psk se#cret\n'
|
||||
'\twpa-scan-ssid 1\n'
|
||||
), lambda device: True)[0]
|
||||
|
||||
found = n.profile(stanza)
|
||||
assert found["ssid"] == "ROGC Classroom", found
|
||||
assert found["hidden"] is True, "the hidden flag was dropped again"
|
||||
assert found["psk"] == "se#cret", "a # in the password was truncated again"
|
||||
assert found["key_mgmt"] == "wpa-psk", found
|
||||
|
||||
# A rollback puts back exactly what it commented out, and nothing else.
|
||||
before = ("auto enp1s0\niface enp1s0 inet static\n\taddress 10.40.0.9/24\n\n"
|
||||
"allow-hotplug wlp2s0\niface wlp2s0 inet dhcp\n\twpa-ssid Sanctuary\n")
|
||||
after = n.comment_out(before, n.wireless(n.parse(before),
|
||||
lambda d: d == "wlp2s0")[0])
|
||||
restored, count = n.uncomment(after)
|
||||
assert count, "nothing was marked, so a rollback would have nothing to undo"
|
||||
assert restored == before, "the rollback did not restore the file exactly"
|
||||
WIFI_CHECK
|
||||
|
||||
# And the command itself carries both halves of the fix.
|
||||
grep -q '802-11-wireless.hidden' /usr/local/bin/classroom-wifi
|
||||
grep -q 'managed", "yes"' /usr/local/bin/classroom-wifi
|
||||
|
||||
# PRESENTATIONS.
|
||||
#
|
||||
# Off by default, with everything it needs present on every machine, so
|
||||
# enabling it on a device is one line in device.conf and a reboot.
|
||||
command -v classroom-presentations >/dev/null
|
||||
command -v classroom-presentation-import >/dev/null
|
||||
command -v soffice >/dev/null
|
||||
command -v pdftoppm >/dev/null
|
||||
command -v pdfinfo >/dev/null
|
||||
[[ -f /etc/systemd/user/ekklesia-classroom-presentations.service ]]
|
||||
# Enabled the same way the audio policy is, so it actually reaches a
|
||||
# session rather than merely being installed. A unit file nobody starts
|
||||
# is a teacher whose download never appears.
|
||||
[[ -L /etc/systemd/user/plasma-workspace.target.wants/ekklesia-classroom-presentations.service ]]
|
||||
[[ -f /usr/share/applications/ekklesia-presentations.desktop ]]
|
||||
[[ -f /usr/share/icons/hicolor/scalable/apps/ekklesia-presentations.svg ]]
|
||||
|
||||
# A classroom that has never heard of the setting comes up without it.
|
||||
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 -c \
|
||||
'from classroom_config import feature; assert feature({}, "PRESENTATIONS_ENABLED") is False'
|
||||
|
||||
# Disabled is the default, so both commands decline to run and say why.
|
||||
classroom-presentations 2>&1 | grep -q 'PRESENTATIONS_ENABLED is false'
|
||||
classroom-presentation-import --once 2>&1 | grep -q 'PRESENTATIONS_ENABLED is false'
|
||||
|
||||
# THE CONVERSION THIS WHOLE FEATURE RESTS ON.
|
||||
#
|
||||
# On a real Debian, with the fonts this package installs. No window, no
|
||||
# splash, no first-run dialog, no editor — measured rather than assumed,
|
||||
# because "LibreOffice is installed" is not the same claim as "a PPTX
|
||||
# becomes slides a teacher can be shown".
|
||||
mkdir -p /tmp/deck
|
||||
python3 <<'MAKE_DECK'
|
||||
page = ('<draw:page draw:name="p%d"><draw:frame svg:width="20cm" '
|
||||
'svg:height="3cm" svg:x="1cm" svg:y="2cm"><draw:text-box>'
|
||||
'<text:p>%s</text:p></draw:text-box></draw:frame></draw:page>')
|
||||
|
||||
open("/tmp/deck/deck.fodp", "w").write(
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<office:document '
|
||||
'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
|
||||
'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '
|
||||
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '
|
||||
'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" '
|
||||
'xmlns:presentation='
|
||||
'"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" '
|
||||
'office:version="1.3" '
|
||||
'office:mimetype="application/vnd.oasis.opendocument.presentation">'
|
||||
'<office:body><office:presentation>'
|
||||
+ (page % (1, "Wednesday Bible Study"))
|
||||
+ (page % (2, "Session 7"))
|
||||
+ '</office:presentation></office:body></office:document>')
|
||||
MAKE_DECK
|
||||
|
||||
HOME=/tmp/deck timeout 240 soffice --headless --norestore --nologo \
|
||||
--convert-to pptx --outdir /tmp/deck /tmp/deck/deck.fodp >/dev/null 2>&1
|
||||
[[ -f /tmp/deck/deck.pptx ]]
|
||||
|
||||
PYTHONPATH=/usr/local/lib/ekklesia-classroom HOME=/tmp/deck python3 <<'CONVERT_CHECK'
|
||||
import pathlib
|
||||
|
||||
import classroom_presentations as library
|
||||
|
||||
ok, error = library.convert_to_pdf("/tmp/deck/deck.pptx", "/tmp/deck/slides.pdf")
|
||||
assert ok, f"PPTX could not be converted: {error}"
|
||||
|
||||
pages = library.page_count("/tmp/deck/slides.pdf")
|
||||
assert pages == 2, f"expected 2 slides, got {pages}"
|
||||
|
||||
made, error = library.render_pages("/tmp/deck/slides.pdf", "/tmp/deck/pages")
|
||||
assert made == 2, f"expected 2 rendered pages, got {made}: {error}"
|
||||
|
||||
# The library asks for page-001.png whatever the page count's width is.
|
||||
for number in (1, 2):
|
||||
path = pathlib.Path(f"/tmp/deck/pages/page-{number:03d}.png")
|
||||
assert path.exists(), f"{path} was not rendered"
|
||||
assert path.stat().st_size > 0, f"{path} is empty"
|
||||
|
||||
# Slide 2 is the one a teacher picks as the card picture, so its text has
|
||||
# to have survived the conversion.
|
||||
text = library.first_text("/tmp/deck/slides.pdf", pages=2)
|
||||
assert "Session 7" in text, f"text did not survive the conversion: {text!r}"
|
||||
CONVERT_CHECK
|
||||
|
||||
# The fonts a Google Slides export asks for, by their real names.
|
||||
fc-list 2>/dev/null | grep -qi 'Liberation Sans'
|
||||
fc-list 2>/dev/null | grep -qi 'Carlito'
|
||||
fc-list 2>/dev/null | grep -qi 'Caladea'
|
||||
# And the one that must NOT be here, because it prompts for a licence.
|
||||
! dpkg -l ttf-mscorefonts-installer 2>/dev/null | grep -q '^ii'
|
||||
|
||||
# THE MANUAL.
|
||||
#
|
||||
# Installed on every machine, works with no network at all, and shows
|
||||
# only the features this device has.
|
||||
[[ -f /usr/local/share/ekklesia-classroom/manual/classroom-manual.html ]]
|
||||
[[ -f /usr/local/share/ekklesia-classroom/manual/classroom-manual.pdf ]]
|
||||
command -v classroom-manual >/dev/null
|
||||
[[ -f /usr/share/applications/ekklesia-manual.desktop ]]
|
||||
head -c 5 /usr/local/share/ekklesia-classroom/manual/classroom-manual.pdf \
|
||||
| grep -q '%PDF-'
|
||||
grep -aq 'Revision' /usr/local/share/ekklesia-classroom/manual/classroom-manual.pdf
|
||||
! grep -qE 'https?://' \
|
||||
/usr/local/share/ekklesia-classroom/manual/classroom-manual.html
|
||||
|
||||
# The manifest the manual reads carries feature NAMES and nothing else:
|
||||
# no broker password, no addresses, nothing else out of device.conf.
|
||||
printf 'PRESENTATIONS_ENABLED=true\nWONDERINK_ENABLED=false\nMQTT_PASSWORD=never-a-teacher\n' \
|
||||
>> /etc/ekklesia-classroom/device.conf
|
||||
classroom-desktop-layout >/dev/null 2>&1 || true
|
||||
[[ -f /run/ekklesia-classroom/features.json ]]
|
||||
grep -q 'PRESENTATIONS_ENABLED' /run/ekklesia-classroom/features.json
|
||||
! grep -q 'WONDERINK_ENABLED' /run/ekklesia-classroom/features.json
|
||||
! grep -q 'never-a-teacher' /run/ekklesia-classroom/features.json
|
||||
! grep -q '10.40.0.2' /run/ekklesia-classroom/features.json
|
||||
classroom-manual --features | grep -qE 'PRESENTATIONS_ENABLED +shown'
|
||||
classroom-manual --features | grep -qE 'WONDERINK_ENABLED +hidden'
|
||||
|
||||
# The panel offers Presentations only where it is enabled, and the three
|
||||
# applications that were already on the bar keep their order.
|
||||
layout=/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
|
||||
grep -q 'var PRESENTATIONS = true;' "$layout"
|
||||
grep -q 'ekklesia-presentations.desktop' "$layout"
|
||||
grep -q 'ekklesia-manual.desktop' "$layout"
|
||||
python3 - "$layout" <<'ORDER_CHECK'
|
||||
import re
|
||||
import sys
|
||||
|
||||
text = open(sys.argv[1]).read()
|
||||
order = re.findall(r'launchers\.push\("applications:([^"]+)"\)', text)
|
||||
expected = ["ekklesia-wonderink.desktop", "google-chrome.desktop",
|
||||
"ekklesia-dvd.desktop", "ekklesia-presentations.desktop"]
|
||||
assert order == expected, f"the dock order changed: {order}"
|
||||
ORDER_CHECK
|
||||
|
||||
sed -i '/^PRESENTATIONS_ENABLED=true$/d;/^WONDERINK_ENABLED=false$/d;/^MQTT_PASSWORD=never-a-teacher$/d' \
|
||||
/etc/ekklesia-classroom/device.conf
|
||||
|
||||
# The policy module and the daemon agree on what they import, on a machine
|
||||
# rather than in a checkout.
|
||||
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 -c \
|
||||
|
||||
Reference in New Issue
Block a user