The release that stops handing out an account to everyone who finds your server.
Read this before upgrading
Your server no longer seeds a demo account (#110)
Every Lyftr ever installed has come with demo@lyftr.local, password password123,
published in the README. Convenient for a look around, considerably less convenient
on a box with a public hostname.
From this release the demo account is only created when DEMO_MODE is on, which
defaults on in development and off everywhere else — Docker Compose included.
If you already have that account, nothing is deleted. Upgrading leaves the row exactly
where it is, and the backend logs a warning at startup naming it and reminding you the
password is public. Removing it is your call, via Settings → Delete account. We are not
going to reach into your database and delete a user that might, by now, be someone's
real training history.
The public demo at lyftr-demo.fly.dev is unaffected; it sets DEMO_MODE=true on purpose.
What's new
You can close registration (#86, #110)
A self-hosted Lyftr behind a reverse proxy has always accepted signups from anyone who
found it. The only fix lived outside the app — a proxy rule, or basic auth over the
whole thing — and neither is something you'd discover by reading the docs.
There is now a REGISTRATION setting with three values:
| Value | Behaviour |
|---|---|
open
| anyone may sign up. The default, so upgrading changes nothing |
first-user
| the first account claims the server; every signup after it is refused |
closed
| no signups at all |
Rejections are a 403 from the API with a plain message, so both the web app and the
phone show you the real reason instead of guessing. When registration is closed the
login screen stops offering "Create account" and says so in a line underneath, and
/register — reached by bookmark or a shared link — explains itself rather than
failing when you press submit. /api/v1/info advertises registration_open so the
apps can tell without probing.
A typo in REGISTRATION stops the server at startup rather than quietly falling back
to open. Believing you are closed when you are not is the one failure worth being
loud about.
On first-user: treat it as a setup mode, not a lock. It keys off the users table
being empty, and the users table lives on the volume — if that volume ever fails to
mount, the server comes up empty and open. This is not hypothetical; it is exactly how
Immich instances have been claimed by strangers. Claim your account, then put
REGISTRATION=closed in your .env. That setting cannot be undone by a bad mount.
The server prints a notice at startup for as long as it is sitting unclaimed.
Fixes
A day is now the day you logged it, not the day your phone thinks it is (#83, #97, #98)
Workouts, weigh-ins and food entries used to work out which day they belonged to at
read time, from whatever timezone the device happened to be in. Fly somewhere and your
history rearranged itself behind you — a Tuesday evening workout became Wednesday
morning, and yesterday's calories moved.
Every day-scoped row now records its own day at the moment you save it. Diary entries
store the date you picked; workouts store the UTC offset where the session actually
started. Reading them back never re-derives anything. This is the same split Fitbit,
Garmin, Strava and Android Health Connect use, and for the same reason.
Existing data is backfilled on first startup — no action needed, nothing to migrate by
hand.
The fix came from @DisplacedForest in #83 — the first outside contribution to
Lyftr, and it landed on one of the least fun parts of the codebase. #97 and #98 are
follow-ups found while testing it on real devices in real timezones.
Bodyweight steps by 0.1 instead of 0.5 (#80, #85, #91)
0.5 kg was too coarse to record a real weigh-in, which meant typing the number by hand
every time. Every stepper in the app now names its own increment rather than inheriting
one, so a bodyweight field steps by 0.1 and a plate-loaded lift still steps by
something sane.
The Load-from-Program dialog opens in the middle of the screen (#100)
It was rendering off-centre with an overlay covering only part of the page — the dialog
was trapped inside a positioned ancestor. It now portals to the top level like every
other dialog.
The finished-rest panel is readable (#101)
Real headline, full-width action button, instead of text competing with the timer for
the same space.
Under the hood
Nothing here changes what you see, but it is most of the diff.
- One shared package (#99, #108). Web and mobile were maintaining two copies
of the same utilities, stores and hooks, and they had started to disagree — the
timezone bug above existed partly because there were two implementations of "what day
is this". Both apps now import one copy from@lyftr/shared. Web moved to React 19
to join the workspace. - 5,704 lines of dead code removed (#94), plus unreachable components, dead config
and stale design docs. - Linting is on for web (#95), and turning it on immediately found a React
hook-order bug that had been sitting there quietly. - Mobile reads its version from the release tag (#89) instead of a number committed
in a file, so the app can no longer disagree with the release it shipped in.
Net for the release: 6,000 lines lighter than v0.1.0-beta.5.
Upgrading
Docker / self-hosted: pull and restart as usual. Migrations run on startup and
backfill the day columns. Verified on a real v0.1.0-beta.5 install — data intact,
existing accounts still sign in, no lockout, and rolling back to beta.5 on a migrated
volume still works if you need to retreat. The one behaviour change is the demo account,
covered at the top.
You do not need to set REGISTRATION to upgrade. Leaving it unset keeps today's
behaviour exactly.
Android (side-load): download lyftr-v0.1.0-beta.6.apk below and open it on your
phone. Installs over an existing Lyftr, keeps your data, same signing key as every
previous release.
An older app talking to a newer server, or a newer app talking to an older server, both
work — a server that doesn't advertise registration_open is read as open, which is
what it is.
Thanks
@DisplacedForest — first outside contributor to Lyftr, and not with a typo fix
either: #83 reworked how the whole app decides what day a workout or a meal belongs to.
Timezones are the thing everyone assumes is simple until they own a bug report from
someone who flew east.
Android (side-load)
Download lyftr-v0.1.0-beta.6.apk below. On your phone, open it and
allow "install from unknown sources" if prompted.
iOS is distributed via TestFlight / the App Store (Apple doesn't allow
side-loading), so it isn't attached here.
What's Changed
- chore(mobile): read the app version from the tag instead of committing it by @Cawlumm in #89
- feat(weight): step bodyweight +/- by 0.1 instead of 0.5 (#80) by @Cawlumm in #85
- refactor(weight): move web bodyweight inputs to StepperTile, drop WeightInput's stepper (#91) by @Cawlumm in #92
- chore: remove 5,704 lines of dead code, dead config, and stale design docs by @Cawlumm in #94
- chore(web): turn linting on, and fix the hook-order bug it found by @Cawlumm in #95
- Fix app-wide timezone handling for workouts, food queries, and server defaults by @DisplacedForest in #83
- Follow-up fixes to the stored-day model, found by manual device testing by @Cawlumm in #97
- Day-model follow-ups: the review findings deferred from #97 by @Cawlumm in #98
- Fix the Load-from-Program dialog rendering off-centre with a partial overlay by @Cawlumm in #100
- Move web to React 19 so it can join the npm workspace by @Cawlumm in #99
- Make the finished rest panel readable: real headline, full-width action by @Cawlumm in #101
- Move all shared logic into @lyftr/shared by @Cawlumm in #108
- feat: let a self-hoster close registration, and stop shipping a public account by @Cawlumm in #110
New Contributors
- @DisplacedForest made their first contribution in #83
Full Changelog: v0.1.0-beta.5...v0.1.0-beta.6