- HTML 45.1%
- Python 20.6%
- Rust 17.5%
- JavaScript 13.9%
- CSS 1.3%
- Other 1.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
image / build (push) Successful in 15s
REG/IMAGE hardcoded registry.bitnest.cc, so this world-readable repo disclosed the lab's internal registry. Both now come from variables (REGISTRY, IMAGE_REPO), as do the site name (SITE_NAME) and the refresh command (DEPLOY_COMMAND) — nothing about the deployment is hardcoded here any more. REGISTRY and DEPLOY_COMMAND are set at the varasys ORG level since they are identical for every site, so a future site needs only IMAGE_REPO + SITE_NAME. Also adds the fail-fast guards from the renew workflow: a missing variable now aborts at step one instead of building an image and pushing it somewhere unintended. Behaviour is otherwise unchanged — same registry, same image repo, same refresh-site call. |
||
| .claude/skills/verify | ||
| .forgejo/workflows | ||
| assets | ||
| docs | ||
| fw | ||
| pico | ||
| pico-cp | ||
| pico-explorer | ||
| pico-scroll | ||
| pico-wm8960 | ||
| rust | ||
| src | ||
| tests | ||
| tools | ||
| .gitignore | ||
| build.sh | ||
| CLAUDE.md | ||
| Containerfile | ||
| deploy.sh | ||
| editor-beta.html | ||
| editor.html | ||
| embed.html | ||
| embed.js | ||
| explorer.html | ||
| grid.html | ||
| guitar.html | ||
| index.html | ||
| info-editor.html | ||
| info-explorer.html | ||
| info-grid.html | ||
| info-kit.html | ||
| info-micro.html | ||
| info-player.html | ||
| info-pm_e-2.html | ||
| info-showcase.html | ||
| info-stage.html | ||
| info-teacher.html | ||
| kit.html | ||
| legacy.html | ||
| LICENSE | ||
| manifest.webmanifest | ||
| micro.html | ||
| mobile-sessions.html | ||
| mobile-sw.js | ||
| mobile.html | ||
| other.html | ||
| player.html | ||
| pm_e-2.html | ||
| README.md | ||
| showcase.html | ||
| stage.html | ||
| sustainable-software.html | ||
| teacher.html | ||
| trainer.html | ||
| VERSION | ||
Polymeter Metronome and Practice Trainer
an example of Sustainable Software
Try it: https://metronome.varasys.io · This page on the web: https://metronome.varasys.io/README.html · Source: https://codeberg.org/VARASYS/metronome
Part 1 — Sustainable software
This app is small, and it works — but that is not really the point of it. The point is to show that software can be built to respect the people who use it, and that almost every annoying, extractive, or manipulative thing you have come to expect from an app is a choice, not a law of nature. Here is every way this one is built differently, in plain terms — on the left, what the software industry would tell you to do; on the right, what this app does instead. It is a demo, so every row is a real, working feature you can check for yourself.
It's yours, and it stays yours
| What most apps do | What this app does |
|---|---|
| Make you sign up first — an account before you can touch anything, so you can be tracked and emailed | No account, ever — open it and start; there is nothing to sign up for |
| Keep your stuff on their servers — where they can mine it, and where you can't just take it and leave | Your stuff stays on your device — your settings and history live in your own browser, uploaded nowhere |
| Route sharing through their system — so a share needs their account and passes through their servers | Sharing is just a link — the link itself carries the whole thing; no account on either end |
| Sync your data to their cloud — in a form they can read, so a copy of you becomes theirs | Sync goes to a place you choose — scrambled on your device first, to a store you own, so it stays gibberish to everyone else |
| Trap your work in a private format — one only their app can open, that becomes unreadable the day the app dies | Your work is plain, readable text — an open format any program can read, today or in twenty years |
| Make leaving painful — no export, a buried "delete", a cancellation maze | Leaving is one tap — take your data with you as a link or a file, whenever you like |
No tricks, no toll booths
These manipulations even have a name — dark patterns: tricks deliberately built into an app to get you to do something you didn't mean to. They are not accidents or sloppiness; they are designed, by people paid to design them. This app simply refuses to use them.
| What most apps do | What this app does |
|---|---|
| Watch everything you do — tracking on by default, and the data sold to whoever pays | No analytics at all — the app never phones home; there is nothing to collect and nothing to opt out of |
| Show ads and farm your attention — because if it's free, you are the product | No ads, nothing sold — you are the user, not the product |
| Nag and guilt-trip you — pop-ups until you give in, and a "no" button that shames you for clicking it | Never nags — it just opens and works; no interruptions, nothing to click past |
| Bill you forever — a subscription, or a "free" trial that quietly starts charging | Free, with nothing to cancel — no subscription, no trial, no bill |
Built to outlive us
| What most apps do | What this app does |
|---|---|
| Rot from the inside — built on a tower of other people's code that breaks when any one piece changes | One small self-contained file — nothing outside it to break; it should still open in a browser decades from now |
| Die when the company does — it needs their servers running, and vanishes when the money dries up | Cannot be shut down — no servers to keep alive, and the code is public, so anyone can keep it going |
| Demand to be online — a constant connection, so they can keep watching and gating you | Works offline, forever — add it to your phone straight from the web (no app store), and after that the network is optional |
| Bloat until your device is "too old" — pushing perfectly good hardware into the landfill | Runs on almost anything — an old phone, or a $4 chip; it asks so little that hardware lasts longer |
Two ways to bring your own data
Out of the box your practice log is kept in your browser's own storage on the device you used, and goes nowhere else. If you want the same log on your phone and your laptop, sync never sends it to this project — it goes to a store you pick. There are two kinds, for two kinds of people:
- A simple key-value store — for anyone who just wants their metronome in sync across devices, with no infrastructure to run. Use the one-tap demo (JSON Blob, no signup) or a free store like KVdb or JSONBin and paste a link. It is a plain "box in the cloud" that only PolyMeter uses — nothing to host or maintain. If you would rather it be your box, any address that answers a read and a write will do: a web server with WebDAV writes turned on is the usual way to run one yourself, or Cloudflare Workers KV if you would rather run nothing at all.
- Your own Solid pod — for people who already keep their data in a home they control. A Solid pod is a standard, portable store for your data that many apps can share — get one from a provider and run nothing, or host it yourself with Community Solid Server, the project's reference server. PolyMeter becomes one guest writing into a place that is yours, not a silo that owns a copy of you.
Sustainable isn't magic
Honesty is part of the point, so here are the trade-offs. The built-in demo sync store is deliberately throwaway — it can reset after a day. That is not a flaw to apologise for; it is what a demo is. The sustainable part is that you can point sync at a store you own and never depend on this project at all. And keeping no analytics has an honest cost: the project genuinely cannot see what people find confusing or where they give up, so the only way an awkward corner ever gets fixed is if someone says so. That is the trade for never watching you — and it is one worth making.
"No analytics" — the honest fine print
The app on your device sends nothing: no trackers, no cookies, and it never phones home, so the copy
you save behaves exactly like the hosted one. The only record a visit leaves is on the web server —
the same kind of access log every website has kept for thirty years — and it is deliberately minimal.
Before a single line is written, your IP address is blunted to a /24 (the last part is zeroed, so
66.234.152.199 is stored as 66.234.152.0, and an IPv6 address to a /48); no full address is ever
saved. Each line records only the time, that masked address, which page was requested, and the bare
mechanics of it (method, protocol, response status, the size of the request and its response, and how
long it took). It does not record
your browser's User-Agent, the site you came from (the Referer), any cookies, or the connection's
encryption details — those are discarded before writing, so they exist in no file, journal, or backup.
Your shared tracks and set lists ride in the part of a link after the #, which browsers never send to
any server, so your patterns never reach the log at all. The detailed lines are deleted after 30
days; the only thing that outlives them is a plain running count — how many page views and how many
downloads there have been, sampled every minute or so and not broken down by page. It carries nothing
about who or what: no address (not even the masked one), no page, and no way to tell one visitor from
another or to link two visits. That count is kept for about a year, so the project can
answer "is anyone still using this?" without keeping anything about anyone. It is the reverse of how
most analytics work: here the bare aggregate is what is kept for a while, and everything that could
identify a request is thrown away fast — keeping the number is precisely what lets the records be
deleted.
Don't take our word for it — here is the site's entire logging configuration, copied straight
from the running server. ip_mask rewrites both IP fields to a /24 (IPv6 /48) inside the encoder, so
the full address is destroyed before the line is written — not stored-then-anonymised, simply never
written. The four delete lines drop the client port, the entire request-header map (that is what
removes User-Agent, Referer and any cookie — not a blocklist but the whole map, so even a header nobody
has heard of cannot leak), the TLS details, and every response header. output stdout sends the line
to the service journal, which expires it after 30 days.
# metronome.<domain> -- the site's entire access-log configuration
log {
output stdout
format filter {
wrap json
fields {
request>remote_ip ip_mask {
ipv4 24
ipv6 48
}
request>client_ip ip_mask {
ipv4 24
ipv6 48
}
request>remote_port delete
request>headers delete
request>tls delete
resp_headers delete
}
}
}
Two footnotes so a sharp reader can't catch us out: this is the per-site block — the surrounding config
only sets up HTTPS and static-file serving, neither of which logs anything; and while request>headers delete removes the Host header from the log, request.host is recorded as a separate field, which is
just which site was asked for (always this domain) and says nothing about you. The block is a snapshot
of infrastructure maintained outside this repo; by standing agreement it is updated here whenever the
masking, deletion, or retention changes there.
Part 2 — What it actually does
Underneath all of that, it is a genuinely flexible metronome. Set a tempo and it clicks — but you can also stack up several rhythms at once, each with its own beat pattern, subdivision and drum sound, to practise polyrhythms and odd time signatures an ordinary metronome can't handle. Add a guitar, ukulele, bass, piano or harmonica on top, each one showing the chord or scale you're working on.
Press Practice and it quietly logs the session — your tempo, your notes — so you can pick up exactly where you left off. Arrange your favourite grooves into set lists, step through them, and share any of it as a plain link. It adds to your phone or desktop like a normal app and keeps working with no connection at all.
Older experiments — the desktop editors and the hardware form-factor prototypes — are still tucked in the app's menu, kept only as a record of how this final version came to be.
Part 3 — How it's built (for the technically minded)
This part is for developers and the curious — skip it if that's not you; nothing above depends on it.
Every deployed page is one self-contained .html file with zero runtime dependencies. build.sh
inlines a shared engine, the seed set lists, base styling and the brand assets into each page; it
asserts that no build markers survive, so what ships is fully offline-capable. Even this page is
proof: the Markdown you're reading is rendered to README.html by a tiny Markdown renderer inside
build.sh — no library, no dependency.
./build.sh # assemble the self-contained pages into dist/ (git-ignored)
./deploy.sh # build, stamp version, mirror dist/ to the Caddy web root
node tests/run.mjs # track-format conformance suite (the cross-implementation contract)
The public site deploys on git push to main (a CI job builds and refreshes the origin).
main is the canonical home of the whole project — the app, the galleries, the firmware, the
tests and docs. The concepts branch keeps only the hardware design work (KiCad / SPICE /
wokwi).
License
GNU AGPL v3 © VARASYS · Simplifying Complexity.