feat: move Student Android into Chromium #29

Merged
dev-bot merged 28 commits from feat/chromium-student-session into main 2026-09-28 21:00:00 -04:00
Collaborator

Summary

  • Replace the Student Flutter launcher and native Student Android window with Chromium at http://localhost:8080/.
  • Serve a simple HTML/JS selector dynamically from the existing Student policy; approved websites remain native top-level Chromium tabs.
  • Configure Chromium to start at the selector with one managed Apps bookmark in the visible bookmarks bar. No Home button, extension, iframe, or generic localhost exception.
  • Render policy-approved Android apps at /android/<approved-package-id> through a loopback-only browser/media broker.
  • Keep Admin Flutter/catalog/installer behavior unchanged.

Media and security boundary

The root-owned broker uses the existing pinned NixOS scrcpy server 3.3 and a pinned, attributed vendored ya-webadb/Tango runtime closure (v3.0.0-beta.3, upstream commit b14c1710cbbfc8a2f474ea8868e6a5c6792d59c3). Vendoring provides reproducibility and supply-chain control; it is not treated as an inherent security guarantee. Notices and the refresh procedure are in helpers/student-browser/THIRD_PARTY_NOTICES.md.

The unprivileged Student HTTP service is fixed to loopback and exact Host/Origin, session/CSRF, and WebSocket subprotocol checks. It re-reads policy for actions, validates package IDs, authoritative scrcpy 3.3 dimensions, bounded touch lifecycle, key allowlist, message framing, bounded queued writes, and cleanup. It never receives ADB, shell, serial, or raw scrcpy control access. The existing root controller remains the lifecycle/policy authority.

The lifecycle fixes preserve the original root/private podman-network bootstrap and explicit adb connect 127.0.0.1:5555 before broker enumeration, prefer that serial when an emulator-5554 alias is also present, preserve the validated session environment through sign-out helpers, attach media listeners before stream start, replay a contiguous keyframe GOP after late WebSocket attachment, forward PCM and audio-disabled status through the production Unix-broker WebSocket path, bind/revoke WebSockets to the owning session, and close the owner broker/controller on final viewer disconnect (with a short reconnect grace period), sign-out, stream failure, or policy revocation. Session expiry is sliding inactivity based on authenticated control traffic; media alone does not keep an abandoned session alive. Reopening the Apps bookmark reuses the valid session cookie rather than invalidating an active viewer. A codec session/configuration delivery failure closes that viewer instead of accepting a keyframe without the matching configuration, allowing a clean reconnect.

Verification

  • npm test --prefix helpers/student-browser — 9/9. The production Unix-broker integration asserts PCM payload forwarding, audio-disabled replay on reopen, startup media ordering, portrait dimensions from the real 3.3 H264 configuration packet shape, contiguous IDR/delta replay and congestion gating, final-viewer teardown/reopen, cookie reuse, sliding expiry, exact Host/Origin/CSRF/WS rejection, malformed framing/input, and sign-out helper environment/failure behavior.
  • node --check helpers/student-browser/server.mjs
  • node --check helpers/student-browser/test/browser-integration.test.mjs
  • git diff --check — working-tree changes clean. git diff --check origin/main...HEAD reports six inherited trailing-whitespace lines in vendored upstream files only; these were not reformatted.
  • nix flake check --no-build
  • nix build .#checks.x86_64-linux.student-chromium-policy --no-link --print-build-logs
  • nix build .#checks.x86_64-linux.student-android-lifecycle --no-link --print-build-logs
  • nix build .#nixosConfigurations.kwilabook.config.system.build.toplevel --no-link --print-build-logs — passed after the latest commit.
  • Real Chromium fixture: ffmpeg/libx264 H264 SPS/PPS + IDR and 48 kHz stereo PCM; observed decoded frames and AudioWorklet-processed samples after a user gesture, including stream error/reopen cleanup. The inspected active-viewer screenshot is .amp/in/artifacts/screenshot-portrait-stream-20260919.png.
  • Latest pushed fix commit: cd4ab17 (fix: preserve Student browser audio delivery).

Limitations

This orb has no real ReDroid/Android device. Real-device ADB/scrcpy 3.3 compatibility, Android app behavior, touch/control integration, live Student session startup, and speaker audibility still require hardware/ReDroid validation. The browser H264/WebAudio fixture validates the deployed frontend pipeline and broker lifecycle, not a real Android device. No Android emulator/AVD or customer device was used. Do not deploy this PR to customer devices without that validation.

Latest UI/audio refinement

  • Commit 75d28af removes the Android viewer top bar, Back to apps button, and Enable audio button. The canvas now occupies the full viewport; browser Back returns to the selector.
  • The viewer attempts WebAudio on load. Managed Chromium adds an exact-origin AutoplayAllowlist for http://localhost:8080, so Student lessons do not require a visible audio control or gesture. An invisible retry remains only for unmanaged fixture browsers that do not apply the enterprise policy.
  • Visual fixture verification: zero buttons, no .topbar, canvas 1280x720 at 1280x720 viewport. Screenshot: .amp/in/artifacts/screenshot-fullpage-android-20260919.png.

Latest pushed HEAD: 75d28af. Checks rerun: npm test --prefix helpers/student-browser (9/9), node --check helpers/student-browser/web/android.js, nix flake check --no-build, student-chromium-policy, student-android-lifecycle, and full NixOS toplevel build.

Latest selector refinement

  • Commit 3f43374 combines approved website and Android entries into one dynamically populated, flat outlined app grid. There are no Web apps or Android apps section headings; website cards still open native top-level Chromium tabs and Android cards still start the approved package and navigate to its local viewer route.
  • Focused selector coverage is included in npm test --prefix helpers/student-browser (10/10). The mixed-app render was inspected at /home/user/workspace/repo/.amp/in/artifacts/screenshot-mixed-approved-apps-20260919.png.
  • Branch HEAD is 3f4337491e790be86a2d7228e7926a3f6a5c441e; no customer deployment or merge was performed.

Latest Student selector/status-bar refinement

  • Commit 1bbe29e removes Sign out from the HTML selector and adds it to the existing native session status-bar pattern. Student starts the launcher binary in KWILABOOK_STATUS_BAR_ONLY=1, so the status bar does not start a Flutter view; it invokes the existing validated kwilabook-seat stop student own-session mechanism. Admin keeps its Flutter launcher and behavior.
  • The combined policy app grid now has one shared addApp rendering path and one shared .app-card style. Website cards remain ordinary top-level <a target="_blank"> navigation; Android cards remain typed buttons for validated start/navigation. Both share equal min-height, centered alignment, typography, focus/hover treatment, and keyboard semantics.
  • npm test --prefix helpers/student-browser passes 11/11, including the mixed-app shared-rendering assertions and Student status-bar/lifecycle ownership checks. nix flake check --no-build and the full NixOS toplevel build pass. The build includes the modified native launcher/status-bar C++.
  • Chromium fixture evidence: computed DOM confirms both mixed cards are flex, centered, 112px minimum height, and the selector has no Sign out text. Inspected screenshot: /home/user/workspace/repo/.amp/in/artifacts/screenshot-selector-shared-cards-statusbar-20260919.png.
  • A native GTK status-bar screenshot was not available in this orb because it has no customer-like Sway/Wayland session; source/lifecycle tests and the full native build cover its ownership wiring. No merge, deployment, emulator, or customer device use.

Latest selector simplification

  • Commit 8b7df24 removes the visible Apps heading and successful-load Choose an approved app instruction. The selector is now only the existing mixed approved-app grid; document title and status accessibility metadata remain.
  • Empty and error status messages remain available, while the empty success status is removed from layout with no reserved space. A small 12px grid inset prevents card borders from clipping without restoring the removed header space.
  • Focused browser tests remain 11/11; nix flake check --no-build and the full NixOS toplevel build pass. Computed DOM evidence confirms title Apps, status role/status live metadata, empty status display, zero top heading/instruction text, 0px status layout, and 12px intentional grid inset.
  • Inspected screenshot: /home/user/workspace/repo/.amp/in/artifacts/screenshot-selector-grid-only-20260919.png.
  • No merge or deployment.
## Summary - Replace the Student Flutter launcher and native Student Android window with Chromium at `http://localhost:8080/`. - Serve a simple HTML/JS selector dynamically from the existing Student policy; approved websites remain native top-level Chromium tabs. - Configure Chromium to start at the selector with one managed **Apps** bookmark in the visible bookmarks bar. No Home button, extension, iframe, or generic localhost exception. - Render policy-approved Android apps at `/android/<approved-package-id>` through a loopback-only browser/media broker. - Keep Admin Flutter/catalog/installer behavior unchanged. ## Media and security boundary The root-owned broker uses the existing pinned NixOS scrcpy server 3.3 and a pinned, attributed vendored ya-webadb/Tango runtime closure (`v3.0.0-beta.3`, upstream commit `b14c1710cbbfc8a2f474ea8868e6a5c6792d59c3`). Vendoring provides reproducibility and supply-chain control; it is not treated as an inherent security guarantee. Notices and the refresh procedure are in `helpers/student-browser/THIRD_PARTY_NOTICES.md`. The unprivileged Student HTTP service is fixed to loopback and exact Host/Origin, session/CSRF, and WebSocket subprotocol checks. It re-reads policy for actions, validates package IDs, authoritative scrcpy 3.3 dimensions, bounded touch lifecycle, key allowlist, message framing, bounded queued writes, and cleanup. It never receives ADB, shell, serial, or raw scrcpy control access. The existing root controller remains the lifecycle/policy authority. The lifecycle fixes preserve the original root/private podman-network bootstrap and explicit `adb connect 127.0.0.1:5555` before broker enumeration, prefer that serial when an `emulator-5554` alias is also present, preserve the validated session environment through sign-out helpers, attach media listeners before stream start, replay a contiguous keyframe GOP after late WebSocket attachment, forward PCM and audio-disabled status through the production Unix-broker WebSocket path, bind/revoke WebSockets to the owning session, and close the owner broker/controller on final viewer disconnect (with a short reconnect grace period), sign-out, stream failure, or policy revocation. Session expiry is sliding inactivity based on authenticated control traffic; media alone does not keep an abandoned session alive. Reopening the Apps bookmark reuses the valid session cookie rather than invalidating an active viewer. A codec session/configuration delivery failure closes that viewer instead of accepting a keyframe without the matching configuration, allowing a clean reconnect. ## Verification - `npm test --prefix helpers/student-browser` — **9/9**. The production Unix-broker integration asserts PCM payload forwarding, audio-disabled replay on reopen, startup media ordering, portrait dimensions from the real 3.3 H264 configuration packet shape, contiguous IDR/delta replay and congestion gating, final-viewer teardown/reopen, cookie reuse, sliding expiry, exact Host/Origin/CSRF/WS rejection, malformed framing/input, and sign-out helper environment/failure behavior. - `node --check helpers/student-browser/server.mjs` - `node --check helpers/student-browser/test/browser-integration.test.mjs` - `git diff --check` — working-tree changes clean. `git diff --check origin/main...HEAD` reports six inherited trailing-whitespace lines in vendored upstream files only; these were not reformatted. - `nix flake check --no-build` - `nix build .#checks.x86_64-linux.student-chromium-policy --no-link --print-build-logs` - `nix build .#checks.x86_64-linux.student-android-lifecycle --no-link --print-build-logs` - `nix build .#nixosConfigurations.kwilabook.config.system.build.toplevel --no-link --print-build-logs` — passed after the latest commit. - Real Chromium fixture: ffmpeg/libx264 H264 SPS/PPS + IDR and 48 kHz stereo PCM; observed decoded frames and AudioWorklet-processed samples after a user gesture, including stream error/reopen cleanup. The inspected active-viewer screenshot is `.amp/in/artifacts/screenshot-portrait-stream-20260919.png`. - Latest pushed fix commit: `cd4ab17` (`fix: preserve Student browser audio delivery`). ## Limitations This orb has no real ReDroid/Android device. Real-device ADB/scrcpy 3.3 compatibility, Android app behavior, touch/control integration, live Student session startup, and speaker audibility still require hardware/ReDroid validation. The browser H264/WebAudio fixture validates the deployed frontend pipeline and broker lifecycle, not a real Android device. No Android emulator/AVD or customer device was used. Do not deploy this PR to customer devices without that validation. ## Latest UI/audio refinement - Commit `75d28af` removes the Android viewer top bar, Back to apps button, and Enable audio button. The canvas now occupies the full viewport; browser Back returns to the selector. - The viewer attempts WebAudio on load. Managed Chromium adds an exact-origin `AutoplayAllowlist` for `http://localhost:8080`, so Student lessons do not require a visible audio control or gesture. An invisible retry remains only for unmanaged fixture browsers that do not apply the enterprise policy. - Visual fixture verification: zero buttons, no `.topbar`, canvas 1280x720 at 1280x720 viewport. Screenshot: `.amp/in/artifacts/screenshot-fullpage-android-20260919.png`. Latest pushed HEAD: `75d28af`. Checks rerun: `npm test --prefix helpers/student-browser` (9/9), `node --check helpers/student-browser/web/android.js`, `nix flake check --no-build`, `student-chromium-policy`, `student-android-lifecycle`, and full NixOS toplevel build. ## Latest selector refinement - Commit `3f43374` combines approved website and Android entries into one dynamically populated, flat outlined app grid. There are no Web apps or Android apps section headings; website cards still open native top-level Chromium tabs and Android cards still start the approved package and navigate to its local viewer route. - Focused selector coverage is included in `npm test --prefix helpers/student-browser` (**10/10**). The mixed-app render was inspected at `/home/user/workspace/repo/.amp/in/artifacts/screenshot-mixed-approved-apps-20260919.png`. - Branch HEAD is `3f4337491e790be86a2d7228e7926a3f6a5c441e`; no customer deployment or merge was performed. ## Latest Student selector/status-bar refinement - Commit `1bbe29e` removes Sign out from the HTML selector and adds it to the existing native session status-bar pattern. Student starts the launcher binary in `KWILABOOK_STATUS_BAR_ONLY=1`, so the status bar does not start a Flutter view; it invokes the existing validated `kwilabook-seat stop student` own-session mechanism. Admin keeps its Flutter launcher and behavior. - The combined policy app grid now has one shared `addApp` rendering path and one shared `.app-card` style. Website cards remain ordinary top-level `<a target="_blank">` navigation; Android cards remain typed buttons for validated start/navigation. Both share equal min-height, centered alignment, typography, focus/hover treatment, and keyboard semantics. - `npm test --prefix helpers/student-browser` passes **11/11**, including the mixed-app shared-rendering assertions and Student status-bar/lifecycle ownership checks. `nix flake check --no-build` and the full NixOS toplevel build pass. The build includes the modified native launcher/status-bar C++. - Chromium fixture evidence: computed DOM confirms both mixed cards are flex, centered, 112px minimum height, and the selector has no Sign out text. Inspected screenshot: `/home/user/workspace/repo/.amp/in/artifacts/screenshot-selector-shared-cards-statusbar-20260919.png`. - A native GTK status-bar screenshot was not available in this orb because it has no customer-like Sway/Wayland session; source/lifecycle tests and the full native build cover its ownership wiring. No merge, deployment, emulator, or customer device use. ## Latest selector simplification - Commit `8b7df24` removes the visible Apps heading and successful-load Choose an approved app instruction. The selector is now only the existing mixed approved-app grid; document title and status accessibility metadata remain. - Empty and error status messages remain available, while the empty success status is removed from layout with no reserved space. A small 12px grid inset prevents card borders from clipping without restoring the removed header space. - Focused browser tests remain **11/11**; `nix flake check --no-build` and the full NixOS toplevel build pass. Computed DOM evidence confirms title `Apps`, status role/status live metadata, empty status display, zero top heading/instruction text, 0px status layout, and 12px intentional grid inset. - Inspected screenshot: `/home/user/workspace/repo/.amp/in/artifacts/screenshot-selector-grid-only-20260919.png`. - No merge or deployment.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kwila/kwilabook!29
No description provided.