sc-tray — the someones.computer system-tray app (docs/tray-app.md in the platform repo)
  • Go 63.3%
  • Shell 18.3%
  • Python 16.4%
  • Dockerfile 0.9%
  • CSS 0.6%
  • Other 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Matthew Baggett 91881198a1
All checks were successful
CI / build (push) Successful in 4m44s
CI / captures (push) Successful in 11m16s
Capture genuine macOS and Windows tray shots, fix the Windows icon (#18)
## What this does

Replaces the fake, CSS-dressed macOS/Windows tray screenshots with genuine
captures, per platform issue
[#648](Grey.ooo/Someones.Computer#648).

| Platform | Scene | Before | After |
|---|---|---|---|
| macOS | `menu`, `running`, `built`, `failed` | Linux GTK menu tinted blue | **Genuine** — captured via `tart` on Apple silicon |
| Windows | `menu` | Linux GTK menu tinted blue | **Genuine** — captured via an unattended Windows Server install on Proxmox |
| Windows | `running`, `built`, `failed` | Linux GTK menu tinted blue | **Not captured this round** — old fakes deleted rather than left mislabelled |

`themes/macos.css` and `themes/windows.css` are deleted; the dressing
pipeline (`run.sh`'s `DRESSED`/`HERO`) is retired entirely rather than kept
half-alive to paper over the Windows gap. `docs/manual.md`'s "dressed for
other platforms" section is rewritten to show what's actually genuine per
platform instead of the retired disclaimer.

## A real bug this surfaced

Windows never showed a tray icon at all, silently — `Shell_NotifyIcon`'s
`LoadImage` call rejects the embedded PNG (it wants a real `.ico`
container) and fails in a way that logs a misleading "operation completed
successfully". Nobody had run genuine Windows before to notice. Fixed with
a platform-specific `icon.ico`, embedded only on Windows
(`internal/tray/icon_windows.go` / `icon_other.go`), rendered from the same
solo-mark SVG by `hack/icons/render.sh`.

## What's still open (partial completion, by design)

This issue explicitly anticipated a partial hand-back being fine. What's
left, tracked back on #648:

- **Windows `running`/`built`/`failed`.** The top-level tray menu opens and
  photographs cleanly (see the screenshot below), but driving *into* a
  submenu (`shop · #12` → a deployment row) never expanded it, keyboard-only,
  on this Windows Server 2022 build — see `hack/tray-vm-capture/README.md`
  for exactly what was tried. A session with real mouse access to the guest
  (Proxmox's `vncproxy` refused every connection attempt this session, for
  reasons unresolved) should just click it.
- **rrru is down.** The Proxmox mule the issue's plan was written around is
  unreachable — confirmed dead at the network layer (ARP failure from the
  bastion on its own LAN segment), not a sandboxing artifact. With the repo
  owner's go-ahead, macOS ran on the operator's own Apple-silicon Mac via
  `tart` instead, and Windows ran on `grey-area` (production) instead of the
  mule — both one-offs, not standing runners.
- **No Packer template, no nightly runner yet.** Both platforms were driven
  by hand this session (documented step by step in
  `hack/tray-vm-capture/README.md`) rather than by the one-command
  automation the issue's "Shape of the run" describes. The Windows
  `autounattend.xml` committed here is "what it should do", not "what ran" —
  a two-CD-ROM confusion in Windows Setup's own media detection meant most
  of the actual install was driven by hand; see the README for the
  workaround that wasn't fully proven either.

## Evidence

**macOS — all four states, genuine:**

![macOS menu](04e3ec4877/docs/images/macos/applications/menu.png)
![macOS running](04e3ec4877/docs/images/macos/applications/running.png)

**Windows — menu, genuine:**

![Windows menu](04e3ec4877/docs/images/windows/applications/menu.png)

(Full set in `docs/images/macos/applications/` and
`docs/images/windows/applications/`, and in the regenerated
`docs/manual.md`.)

## Verified

- `go build ./...`, `go test ./...` clean on darwin/arm64 (native)
- Cross-compiles clean for `GOOS=windows GOARCH=amd64` and confirmed
  rendering a real icon + real menu on an actual Windows Server 2022 guest
- `hack/lint.sh` (golangci-lint v2.13.2): 0 issues
- `shellcheck -S warning` clean on the touched/new shell scripts
- `python3 hack/screenshot/manual.py check`: `docs/manual.md` matches what
  a rebuild produces
- Proxmox VM (grey-area, vmid 104) destroyed after capture; temporary ISOs
  and helper servers on grey-area removed; local `tart` VM stopped (kept,
  not deleted, in case a follow-up session wants to reuse the ~25GB base
  image rather than re-pull it)

Closes #648 is deliberately **not** in this PR body — the issue stays open
for the Windows remainder above.

Reviewed-on: #18
2026-09-07 16:14:52 +00:00
.github Bump golangci-lint to v2.13.2 so it can read Go 1.27 export data 2026-09-02 10:55:55 +01:00
cmd Bump golangci-lint to v2.13.2 so it can read Go 1.27 export data 2026-09-02 10:55:55 +01:00
docs Capture genuine macOS and Windows tray shots, fix the Windows icon (#18) 2026-09-07 16:14:52 +00:00
hack Capture genuine macOS and Windows tray shots, fix the Windows icon (#18) 2026-09-07 16:14:52 +00:00
internal/tray Capture genuine macOS and Windows tray shots, fix the Windows icon (#18) 2026-09-07 16:14:52 +00:00
vendor Bump scclient so sc-tray's poll stops truncating past the first page (#15) 2026-08-18 20:18:22 +00:00
.golangci.yaml Bump golangci-lint to v2.13.2 so it can read Go 1.27 export data 2026-09-02 10:55:55 +01:00
.goreleaser.yaml Scaffold sc-tray: applications and deployments as a system-tray menu 2026-08-08 13:28:58 +02:00
coverage.floor Draw a signed-out menu, instead of exiting to a stderr nobody reads 2026-08-18 17:04:08 +02:00
go.mod Bump scclient so sc-tray's poll stops truncating past the first page (#15) 2026-08-18 20:18:22 +00:00
go.sum Bump scclient so sc-tray's poll stops truncating past the first page (#15) 2026-08-18 20:18:22 +00:00
README.md Capture genuine macOS and Windows tray shots, fix the Windows icon (#18) 2026-09-07 16:14:52 +00:00

someones.computer_tray

sc-tray — the someones.computer system-tray app. Sits in the tray showing your applications and their deployments, and switches them on and off. The full plan, UX and phasing live in the platform repo's docs/tray-app.md (rendered: docs/tray-app.html).

Status: v1, menu-only

Works today Stubbed, and what it waits on
Tray icon + menu on Linux (D-Bus SNI) and Windows; macOS builds locally (cgo)
Application list and per-deployment status, resynced every 10 s Device-flow login — platform /cli/auth/*; today it reads sc login's credential store or $SC_TOKEN
Per-deployment toggles — Activate, and a confirming second click to stop; each row offers only the gesture the platform will accept Rich windows — the runway chart, schedule editor and log viewer are v2, in Fyne
Follows an activation to running/failed and raises one desktop notification (macOS, Linux) Press-and-hold to stop, with an animation: needs a real window, so it lands with v2
A refused activation surfaces a Top up credit… row pointing at the org's billing page
Says who it is signed in as, and against which endpoint
An icon that follows the OS theme — the brand's solo mark as a macOS template image, redrawn black-on-light and white-on-dark by the menu bar itself Per-state icons — animated while deploying, badged on failure, dimmed when signed out
Applications grouped under their organization, whose heading carries the balance, a block-element sparkline of the day's spend, and the runway
An unread-notification row, shown only when there is something unread
Shared auth with sc via scclient

Run it

go build ./cmd/sc-tray
SC_ENDPOINT=https://someones.computer ./sc-tray   # token from `sc login`, or $SC_TOKEN

Linux needs a StatusNotifierItem host (KDE, MATE/XFCE trays, GNOME with the AppIndicator extension).

The menu's shape is drawn once from the first fetch: every later poll updates titles and shows or hides the two gestures, but an application or deployment that appears afterwards asks for a restart on the status line rather than half-drawing. Rows can be shown, hidden and retitled in place, which is all a toggle needs; rebuilding the tree as it changes is a v2 concern, alongside the Fyne rich windows.

The tray menu, captured on Linux

A Linux capture — the manual has the same menu on macOS and Windows too, taken off real hardware/VMs rather than dressed up. The manual is the tour of every state, generated from the capture run below.

Switching off asks twice. A running deployment's row holds a Confirm stop <name> entry rather than stopping on the click that opens it — teardown removes a live stack, and a tray row is easier to fat-finger than a page button. Switching on is a single Activate <name>: its failure mode is a deploy, not an outage. Only the gesture the platform would accept is shown, so a click never comes back 409.

Policy: every feature is proved by a picture, and the pictures are the manual

A tray app is only ever seen as a menu, so a menu nobody has looked at is an untested app. Go tests here can check the labels a function returns; they cannot tell you the applications ended up underneath Quit, which is exactly what the first capture found. So:

  1. Every feature carries a capture — one scene per state a user meets. hack/screenshot runs the real binary against a fixed scene on a headless desktop and photographs the menu at each step. There is a scene for being signed out, for an unreachable platform, for the empty case, for each state a deployment can be in, for a refused activation, for one in flight, and for an application appearing or disappearing under a menu whose shape is already fixed. A change that alters what the menu shows, offers or hides re-runs the suite and commits the result — the diff in docs/images/ is the review.
  2. The captures are read for defects, not just attached. They exist to be looked at. Anything that reads wrong in a shot — order, wording, a gesture offered when it shouldn't be — is a bug found before a user found it. What the current suite found on its way in is filed as platform #838, #839, #841 and #844.
  3. What a machine can check, it checks. cmd/menudump prints the tree the running binary exported over DBus, and check.sh diffs each scene's dumps against that scene's golden — labels, order, nesting, and which rows are disabled or hidden. The same program routes the walk: menudump --to turns a named row into the keystrokes that reach it, so a row that moved is re-reached and a row that is gone fails the run. Counting keypresses instead is how two committed captures came to show rows they did not claim to.
  4. The captures compose into the user manual. docs/manual.md is every state, in the order a person meets it, illustrated by the capture that state produced and quoting the menu that walk recorded. It is generated from the same run, so it cannot drift from the app: a stale screenshot in it means a stale run, and re-running fixes both. CI fails a manual that is not what the scenes and captures produce.
  5. A shot claiming to be a platform has to come off that platform. The suite captures every state in Linux, undressed. macOS and Windows are captured for real too — off a Mac and off a Windows VM, one scene at a time by hand rather than by the automated Linux harness — shown together at the end of the manual. Neither is complete yet: macOS has all four applications states, Windows has one (#648 tracks the rest). What never happens again is dressing a Linux capture in another platform's colours to stand in for the real thing — see hack/screenshot/README.md and hack/tray-vm-capture/.

Gates

Same shape as the platform repo's, in Go terms — one definition each, so a laptop and CI cannot drift:

Check Command Is
Static analysis hack/lint.sh golangci-lint from a pinned image: staticcheck, govet, errcheck, unused, plus bodyclose, errorlint, gosec, gocritic, misspell. Configured in .golangci.yaml, no baseline — the gate is "no findings"
Coverage hack/coverage.sh a ratchet: every package's figure is recorded in coverage.floor, and a drop fails. --bump raises them
Captures hack/screenshot/run.sh every scene, photographed, then asserted, then assembled into the manual — see the policy above. --bless accepts a changed menu. Runs in CI too, against the current binary rather than the committed captures
Menu hack/screenshot/check.sh the same menus as text, one golden per scene under hack/screenshot/scenes/. The half a machine can check, so a row moving fails the build rather than waiting to be spotted in a PNG
Manual hack/screenshot/manual.py check that docs/manual.md is what the scenes and their captures produce, and that no capture is unclaimed and no claimed capture missing — the discipline the platform repo's make lint-screenshots applies to its user guide
Icons hack/icons/render.sh rasterises hack/icons/*.svg into the two PNGs the binary embeds. Run it when the mark moves; the SVG geometry is copied from the platform repo's templates/_mark.html.twig

Those first three files are duplicated verbatim in someones.computer_scclient and someones.computer_agent, and are meant to stay identical.

The figure is low here on purpose, and the ratchet is why it can be. Most of this package is menu callbacks the OS drives and systray mutation that needs a display; a line-coverage target would be met by testing the wrong things. The capture suite is what actually exercises those paths, and the ratchet stops the number rotting downwards while that suite grows to replace it — it started at 17% and the org tree brought it to 27.7%, which is the ratchet doing its job in the direction it is supposed to turn.

Shared code

The /cli client and the credential store come from the scclient module — the wire contract shared with sc, vendored here so CI builds offline (the module is private and the Actions token is repo-scoped). Inside the platform repo's checkout, its go.work resolves scclient by path instead, so contract changes are developed against both consumers at once.