Brasenia (watershield) carpets New England ponds with small floating leaves — little panes resting on the water's surface. brasenia is the Lentago Labs shared viewport: one always-on wall display whose content is chosen by a rubric, not a remote. Panes surface when there's something worth showing — a PR awaiting review, a failing backup, a deploy in flight — and sink back when they resolve; the daily Family Brief is the resting surface underneath. Today it drives a 4K Roku in the office via a live HLS stream; the pane bus and compositor are the roadmap.
(Renamed from lunaria on 2026-07-20, hours after creation — Lunaria annua
is a European garden escape, and the Lentago codename roster is New England
natives only.)
Authorship: The code and documentation in this repo are co-written with Claude (Anthropic). I direct the work and review the output; Claude writes the code and prose. I'm an infrastructure operator, not a software engineer — please don't read this repo as a portfolio of coding ability.
brasenia is a product repo with no build step — so the interesting patterns are in how change is governed, not in a pipeline. Each row links to the evidence in this checkout.
| Pattern | How it shows up here |
|---|---|
| Required check gates every merge — PR-gated change as a hard control, not a norm | The branch ruleset on main requires the docs-check / docs-check context to pass; merges are squash-only with force-push and deletion blocked. The check itself is .github/workflows/docs-check.yml. |
| Shared reusable workflow, not copy-pasted CI — the DRY lesson for platform teams | docs-check.yml is a thin wrapper: uses: lentago/shared-workflows/.github/workflows/docs-check.yml@v1.2.3. Fleet CI logic lives in one place, consumed at an immutable semver tag (shared-workflows ADR-0005); fixes arrive as a reviewable tag bump. |
| Least-privilege agent — scoped permissions and an explicit command allowlist | .github/workflows/claude.yml grants only contents/pull-requests/issues/id-token/actions:read and passes an allowed_tools allowlist of specific git/gh commands to a bot that can push and open PRs. |
| Reversible pause, documented — turn automation off without losing its config | claude-code-review.yml was switched from pull_request to workflow_dispatch with a dated >>> DISABLED 2026-06-25 <<< rationale, then retired once CodeRabbit became the org-wide advisory reviewer; the repo-specific review prompt is preserved at that permalink for re-enabling. |
| Ownership boundary encoded in docs — app repo vs. infra-as-code repo | The What's deliberately elsewhere table and CLAUDE.md draw the line: product/concept/client here, runtime/provisioning in kalmia. It's a convention, not code-enforced. |
| Branding as code — one generator, no per-repo drift | The header comment at the top of this file marks the banner + badge rows as generated by lentago/.github → brand/generate.py; you regenerate there, never hand-edit. |
| Secrets stay out of the repo — concrete hygiene tied to a real deploy | scripts/deploy-roku.sh reads ROKU_IP and ROKU_DEV_PASS from the environment only; CLAUDE.md forbids committing or baking either into the script. |
| Honest rename debt — legacy names flagged and linked, not quietly left | Until it was paid, this README and CLAUDE.md cited kalmia#63 for the pending lunaria → brasenia runtime rename instead of blessing the inconsistency. It was completed through runtime on 2026-10-29: kalmia's role, units, service user and LXC 118's hostname are brasenia, and the container was rebuilt to get there (kalmia#211). |
docs/concept.md— the product concept: pane / rubric / compositor model, the pane contract, rubric v1, the bus contracts (Contracts v1), migration phases.docs/adr/— architecture decisions, reconstructed 2026-08-13 from repo history and fleet records.roku-app/— the BrightScript dev channel the TV runs: a full-screen HLSVideonode with the mandatory auto-retry handler (a bare Video node never recovers from a publisher restart; this one rejoins ~1 s after the stream returns), and the remote: Up/Down peeks at the panes at once as images over the live video, changing nothing (ADR-0012), and Left/Right sets the default resting pane, the one shown when nothing urgent is up (ADR-0013).compositor/— the Phase 2 compositor (stdlib Python 3.11+): ranks the pane bus by rubric v1 and publishesviewport/current.json, the pointer every client follows; falls back to the briefing, then a status card. It also writesviewport/rubric.html, the rubric board, a page showing what is on screen and why (http://pub.lan/viewport/rubric.html), andviewport/configure.html, where the household overrides the rubric's priorities, briefing age limit and default resting pane (ADR-0011). Run aspython3 -m compositor --webroot /srv/www; tests withpython3 -m unittest discover -s testsfromcompositor/.producers/— pane producers for the viewport bus:producers/focus/, which turns fresh session beacons into one open-pull-requests pane per repo;producers/pipeline/, which shows drosera's change pipeline while a change is in flight; andproducers/family-brief/, which turns the morning Family Brief into a today-and-tomorrow pane and a this-week pane;producers/home/, which turns Home Assistant's states into a home pane and a weather pane; andproducers/fleet/, which shows the claytonia bullpen's workers, queue, runs and agent PRs. These five are the resting panes the Roku remote's Left/Right chooses the default from (ADR-0013, ADR-0014).producers/grafana/renders Grafana Cloud dashboards into more resting panes, with a Viewer token pub holds (ADR-0017).bin/pane— claim, renew, release and list panes on the viewport bus (stdlib Python 3.11+); tests withpython3 -m unittest discover -s bin/tests.docs/claiming-the-display.md— the governance page: what deserves the wall, the classes, the two rules, and how to usepane.scripts/deploy-roku.sh— zip + sideload via the Roku dev installer (digest auth; credentials via environment, never committed).
Brasenia follows the fleet's separation of product from provisioning:
| Piece | Where it lives |
|---|---|
| LXC 118 guest definition | kalmia terraform/containers.tf (CI-applied) |
| Runtime stack (the renderer, encoder and HLS server, systemd units) | kalmia roles/brasenia/ + docs/brasenia.md |
Brief publishing (Google Drive → pub.lan) and the home producer's Home Assistant token: pub holds the credentials (ADR-0014) |
kalmia roles/pub/ |
| The Grafana Cloud service account the grafana producer's token belongs to (ADR-0017); the token itself is placed on pub, with kalmia's unit | drosera terraform/ |
The running container is credential-free by design: its only input is
http://pub.lan/. This repo owns the concept, the TV client, and (Phase 2)
the compositor.
producers on pub (LXC 114) → viewport/panes/ → compositor
→ http://pub.lan/viewport/current.json (the pointer; the Family Brief
pane, family-today, is the resting surface)
▼
viewport LXC (118, pve4): headless Chromium renders the pointer's pane, one
band (ADR-0020) → 3840×2160 frame.png
→ ffmpeg (image2pipe, HEVC 5 fps + AAC) → HLS on tmpfs → :8080
▼
Roku dev channel (this repo's roku-app/) → office Roku, 4K display
The 720p play-room Roku this first drove was retired on 2026-10-28; ADR-0009 records the switch to 4K HEVC.
Glass-to-glass latency runs 7–17 s depending on playlist join depth; the whole
chain survives publisher restarts unattended. Validation evidence and the
operational gotchas (mediamtx ?cookieCheck=1 redirect, Roku retry behavior,
headless-Chromium cache staleness) are summarized in docs/concept.md.
- Phase 2 state — you can run the compositor, the focus pane and the
panecommand today; the change-pipeline pane is in progress. Where each piece runs, and why, is ADR-0008. - Phase 2 (in progress) — NAS pane bus (
web/viewport/panes/<pane>/+manifest.json, created 2026-10-07) and the rubric compositor, running on pub (LXC 114) and publishinghttp://pub.lan/viewport/current.json. Compositor v1 (compositor/) implements rubric v1; its runtime unit is kalmia#140. Briefing and Grafana become the first two pane classes, with per-repofocuspanes from the session heartbeat. - Phase 3 — governance snippet rolls out to all local Claudes and the claytonia fleet; panes start arriving from real activity (PR queue, job status, HA alerts).
These systems are real, and nothing critical rides on them. That makes this a safe place to try a change before you make the same kind of change in your own shop. Pick one:
Add a required CI check via the shared-workflows pattern.
Edit a workflow in .github/workflows/ to call a reusable workflow from
lentago/shared-workflows (or change the reusable workflow itself), then open a PR. The branch
ruleset requires that check's context to pass before merge — so the change first proves itself on
its own PR, the same way docs-check now guards every markdown edit here. No apply happens on
merge; the payoff is the gate itself.
Proof this works: #6 — Adopt the shared docs-check workflow.
Evolve the product concept or client through review-gated PRs.
Edit docs/concept.md, the README, the Roku app, or the validation artifacts on a branch and open
a PR; docs-check runs and must pass, and there are no direct pushes to main. Runtime or
provisioning changes for the same feature are filed separately in kalmia — never forked into this
repo, per the ownership split above.
Proof this works: #1 — Rename product lunaria → brasenia,
#2 — Cite kalmia#63 for the runtime rename,
#3 — Preserve the 2026-07-20 HLS validation artifacts.
Repoint what the wall display actually shows (cross-repo — not applied from this repo).
brasenia owns the Roku client and the stream-URL contract only; nothing here has a CI-applied
runtime effect. The display follows the compositor's pointer; to pin it to one page instead you set kalmia's brasenia_pin_url (and
terraform/containers.tf or roles/brasenia/) and open a PR there: kalmia's terraform workflow
applies on merge to main via the LAN self-hosted runner, and then a manual on-host play run
repoints the running stream. Merging in brasenia updates the repo and docs — never the live TV.
This vector needs write access to the kalmia repo and access
to the on-prem self-hosted runner (on-prem: the machines in our own building).
🌱 Lentago Labs is a pro-bono operations practice for organizations that run on volunteers, donations, and one overworked tech person. Everything here is free to take, and we practice what we publish: our own estate runs this way, in the open. Start at the org profile.