# Breathly: a small Expo app for guided breathing exercises

> Breathly is a React Native and Expo app for breath training, with guided animations and a set of named breathing techniques, published to the App Store, Google Play and F-Droid. The repository is the source of a personal project the author built in his free time, and the test setup is the interesting part: Jest for units, Maestro for end to end flows, and a screenshot comparison script for visual regression.

**mmazzarolo/breathly-app** — A tiny breath training app built with React-Native

- Repository: https://github.com/mmazzarolo/breathly-app
- Website: https://breathly.app
- Stars: 597 · Forks: 96
- Language: TypeScript
- License: MPL-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/mmazzarolo-breathly-app

## A guided exercise app, deliberately small

Breathly is described as an open-source React-Native mobile app that lets you focus on your breathing, for daily relaxation and breath training, by choosing a technique and following the guided exercise. The project description calls it a tiny app, and the README repeats that it was developed in the author's free time to refine his React Native knowledge and test new shiny things.

That framing is worth keeping in mind when you read the rest of the repository. This is one person's learning project that happens to be shipped on both app stores, and the author says he hopes the source will be useful to someone. The homepage is breathly.app, and the app is on the App Store, Google Play and F-Droid, with a Product Hunt listing too.

The technology list is short and contemporary. It is a TypeScript project, the primary language recorded for the repository, built on Expo, with React Navigation for navigation, Expo audio for the sound, and the usual Expo modules for assets, fonts, build properties and the dev client. The UI components come from @expo/ui and the icons from @expo/vector-icons, and AsyncStorage and a masked view and a picker are the other runtime dependencies.

The interesting content is the breathing techniques, and the author is candid about where they came from. They are the same ones used in a bunch of other iOS apps, and the descriptions were obtained with a Google search. The apps named as inspirations are Breathe+ Simple Breath Trainer, iBreathe, Daily Calm's deep breathing app and Oak. The breathing animation itself is a copy and paste of different animation styles found online, described as nothing fancy, and the voice lines were personally bought from a voice service rather than generated.

That is a useful thing to know for anyone considering the app for anything more serious than a relaxation exercise.

## The validation scripts are the real engineering in this repository

The package manifest contains more test and validation scripts than build scripts, and that is the tell about how the project is put together.

The unit layer is Jest, run with --runInBand so the tests execute serially, with a watch mode for development, a jest.config.js and a jest.setup.js. Formatting and linting are oxfmt and oxlint rather than the prettier and eslint defaults, with a check mode for the formatter and a fix mode for the linter, and the type check is tsc --noEmit.

Then there is the end to end layer, and it uses Maestro. The e2e script runs the flows in a .maestro/flows directory, and there are platform-specific variants for Android and iOS. The build scripts for that layer exist as separate targets, running expo run:android with a release variant and no bundler, and expo run:ios with a Release configuration and no bundler, so the end to end tests run against a release build rather than a development one.

The visual regression layer is a pair of shell and node scripts under scripts/visual-e2e, one that captures the installed app and one that compares the captures. Running a Maestro flow against a real device and then diffing the screenshots is a heavier setup than most hobby apps carry, and it is the part of this repository most worth copying.

The aggregate targets tie it together. validate:static runs the type check, the lint, the format check and the tests in sequence, and validate:expo runs expo install --check followed by expo-doctor, which verifies that the installed dependency versions still match what the Expo SDK expects. validate:bundles does an expo export for all platforms into a directory, which catches the class of build error that only appears at bundle time. And validate runs the static and Expo validations one after the other.

The package manager is bun, which is visible in the scripts invoking bun run, and the lockfile is a bun.lock.

## Expo with a dev client, and the App Store build in the same tree

The dependency list and the scripts point to a particular way of working with Expo, and it is worth understanding before you clone it.

The project is on Expo SDK 57, with expo, expo-asset, expo-audio, expo-font, expo-build-properties and expo-dev-client all in the same version range, which is the standard way to keep an Expo project on one SDK. The dev client is the notable one, because it means the app runs as a development build rather than inside Expo Go, which is what you need for the native modules the app uses.

There is a masked view and a picker as direct dependencies, and the masked view is a common companion to a navigation library that draws its own header. The color package at version 4 is a small utility for colour manipulation, which for a breathing app with light and dark themes is doing real work.

The build pipeline is set up around Expo Application Services, with eas.json at the root and the e2e build targets passing a release variant with --no-bundler, which is the flag that tells the build to use the bundle that is already in the project rather than starting a dev server. That is what makes a Maestro run against a release build possible on a device without a development server attached.

The result is that the same repository produces a local development experience, a release build for both platforms, and a store submission, with the scripts directory and the .maestro flows sitting alongside rather than in a separate build repository. For a project of this size that is a sensible amount of infrastructure, and for a larger team it is the shape most React Native projects converge on anyway.

## The repository layout is an Expo project with release plumbing attached

The top-level entries describe a standard Expo project with the release and test tooling layered on top.

App.tsx is the application entry, alongside app.json for the Expo configuration and eas.json for the Expo Application Services build and submit settings. The src/ directory holds the source, and assets/ holds the images and fonts. The babel.config.js and tsconfig.json are the usual Babel and TypeScript configuration, with an .oxfmtrc.json and .oxlintrc.json for the formatting and linting tools.

The dot-directories at the root are the interesting part. There is .expo-shared/ for the shared Expo configuration, .maestro/ for the end to end flows, .github/ for the workflows, .vscode/ for editor settings, and .stores/ which is a project-specific directory rather than a standard one. A .nvmrc pins the Node version for the project, and .stores/ alongside .expo-shared/ suggests a convention for keeping generated or downloaded content out of the way.

The scripts/ directory holds the visual end to end helpers and is the counterpart to the .maestro/ directory, since one drives the flows and the other drives the comparison.

The release naming in the tag list is unusual and worth a note, because it tells you how the build pipeline is set up. The listed releases are android-2.2-(3145750), ios-2.2-(18) and android-2.2-(3145748), so the platform comes first, then the app version, then a build number in parentheses. The android builds use a long numeric identifier while the iOS build uses a short one, which is consistent with the separate build counters each store expects.

The last push was on 2026-09-14 and the repository is not archived, so the app is still being worked on even though the latest tagged builds are from the end of July.

## Distribution across three stores, and one of them is not maintained by the author

The distribution story is where a hobby app and a supported product differ, and the README is explicit about it.

The app is on the App Store under the Breathe name, on Google Play under the com.mmazzarolo.breathly package, and on F-Droid. It was also featured on Product Hunt.

The F-Droid entry carries a caveat that matters if you install from there. The README says that currently the author is not keeping the F-Droid version up to date, nor was he the one who created it, and invites anyone to update it as needed with a promise of help. So the F-Droid build is maintained by the community and may trail the store versions.

That is a reasonable arrangement for a project of this size, and it is a common one in the F-Droid ecosystem. It is also a reminder that the version numbers differ between the three channels, so a bug report against one build may not apply to another.

The contributing section asks for pull requests, issues for ideas and feedback, and repeats the F-Droid point, which is where most of the practical contribution effort is available.

The licence is MPL-2.0, which is the file-level copyleft, and the badge is in the README. For an app that people might fork and publish under their own name, that licence requires the modified files to stay open while letting the rest of the app be whatever the distributor chooses, which is a reasonable middle ground for this kind of project.

The credits section is a nice touch for a project of this size, listing the icon made with SVGWave and edited in Affinity Photo, the App Store screenshots made with Screenshots.pro, the Play Store screenshots from Mockuuups Studio device mockups, the animation styles found online, the four apps that inspired the technique descriptions, and the voice lines bought from a voice service.

## Conclusion

Breathly is worth reading if you want to build a small Expo app with a proper verification pipeline, because the scripts in the package manifest are a compact example of a project that checks types, lint, formatting, unit tests, Expo dependency health and a bundle export in one command, and then runs real device flows through Maestro. It is the wrong tool if you want a breath training app with clinical features or a study behind the protocols, since the techniques are described as the same ones other apps use. Before you build on the setup, verify three things yourself: that the Expo SDK version the project pins is one your toolchain supports, that the visual comparison script has a baseline set of images to compare against, since a visual regression test without baselines compares nothing, and that your own store metadata and signing are set up, because the release scripts in the manifest assume an Expo Application Services configuration rather than a bare local build.

## FAQ

### What is the Breathly app?

It is an open source React Native and Expo app for breath training and relaxation, where you pick a breathing technique and follow the guided exercise. The author describes it as a tiny app built in his free time to refine his React Native knowledge, and it is published on the App Store, Google Play and F-Droid.

### Where do Breathly's breathing techniques and animations come from?

The techniques are the same ones used in other iOS apps and their descriptions were obtained with a Google search, with Breathe+ Simple Breath Trainer, iBreathe, the Daily Calm deep breathing app and Oak named as inspirations. The animation is a copy and paste of different animation styles found online, and the voice lines were bought from a voice service.

### How does the Breathly repository test its code?

Jest runs the unit tests with --runInBand, Maestro drives end to end flows in a .maestro/flows directory for Android and iOS against release builds, and a pair of scripts under scripts/visual-e2e capture the installed app and compare the screenshots for visual regression. The validate scripts chain the type check, lint, format check, tests, expo install --check, expo-doctor and a bundle export.

### Is the F-Droid version of Breathly up to date?

Not necessarily. The README says the author is currently not keeping the F-Droid version up to date, nor was he the one who created it, and invites the community to update it and offers help. The App Store and Google Play builds are the ones the author ships.

### What licence is Breathly released under?

MPL-2.0, which is the file-level copyleft licence. The repository is not archived, the last push was on 2026-09-14, and the most recent tagged builds are the Android and iOS 2.2 releases from 2026-07-29.

## Sources

- [License: MPL-2.0](https://github.com/mmazzarolo/breathly-app/blob/master/LICENSE)
- [mmazzarolo/breathly-app on GitHub](https://github.com/mmazzarolo/breathly-app)
- [Project website](https://breathly.app)
- [README](https://github.com/mmazzarolo/breathly-app/blob/master/README.md)
- [Releases](https://github.com/mmazzarolo/breathly-app/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mmazzarolo-breathly-app
