Contribute
Changes reach main through pull requests only. The main branch is protected. A pull request must pass the required status check ci before it can merge.
The commands on this page are untested: CI does not run them.
Send a change
- Set up a checkout. See Build the app.
- Create a branch from the current
main. - Make small commits. Each commit does one thing and leaves the tests green.
-
Run the gate before you push:
make ciThe gate needs
shellcheck,prettier, andmarkdownlinton yourPATH. See Test and CI. - Push the branch and open a pull request against
main. - Wait for the check
ci. Fix the failures and push again. A new push cancels the older run. - A maintainer merges with a rebase merge. Each of your commits lands on
mainas it is, so write each message for the history.
Do not push to main directly.
Write commit messages
Use one line in the form type(scope): brief description. Name the specific change in the description.
feat(gui): show the bundle icon in the frozen About box
fix(app): set LSMinimumSystemVersion from bundled binaries, add copyright
docs: add the manual, the developer guide, and the README
- Use a short type such as
feat,fix, ordocs. - Add a scope in parentheses when it helps. Examples:
gui,app, andparser. - Write the description in the imperative mood and keep it short.
Follow the code rules
- Style. Ruff formats and lints the code: 100 columns, Python 3.12, and the rules
E,F,I,B, andUP. Runmake fmtto fix style. - Tests. Add or update tests with the change. A test that needs a model, a voice, a JVM, or a network carries a marker, and the gate skips it. See Test and CI.
- No network in the gate. A gate test never reaches a real host. GUI tests use the fake service.
- Never swap a name. Lookups and the lexicon change how a word sounds, never which word is said (rule R5). A test such as
NaHCO3staying “N A H C O three” guards this rule. - Shared types.
cloudyreader/models.pyholds the contracts that every module imports. Change it with care, and update every user of a changed type. - Dependencies. Pin exact versions in
pyproject.toml. Re-lock with the safe command on Build the app. Check thatuv.locknames the public index. - Generated files. Never commit
.venv/,.data/,.cache/,dist/, orbuild/. The.gitignorefile lists them.
Change the version
The version lives in three places. Change all three together:
versioninpyproject.toml. The bundle’sInfo.plistand the package metadata read it.__version__incloudyreader/__init__.py. The HTTPUser-Agentof lookups uses it.versionin thecloudyreaderentry ofuv.lock. Re-lock with the safe command on Build the app to update it.
Edit the documentation
The site lives in docs/. GitHub builds it from the branch main, folder /docs, with the theme that docs/_config.yml names. Each page is a Markdown file with front matter:
---
layout: page
title: Voices and rotation
nav_group: Manual
nav_order: 3
permalink: /manual/voices/
description: One sentence that the page shows under its title.
---
nav_groupisManualorDeveloper.nav_ordersorts the navigation. Keep each value unique within its group.- Write a link as
[text]({{ '/manual/voices/' | relative_url }}). Never write the repository path or the domain into a link. - Add an image with
{% include figure.html src="window-light.svg" alt="..." caption="..." %}. Pass the-lightfile name. For a window snapshot, addframe="window". Alt text is required. - Load no third-party script, style, font, or tracker. The site must work with no network except the page itself.
- Write in American English with short, active sentences and the second person. Leave out marketing words and emoji.
- Check every claim against the code. Document what the code does.
- Run
python3 scripts/check_site.pybefore you push. It checks the front matter, the links, the alt text, and the navigation order. - Do not edit or delete
docs/CNAME. GitHub created it. It holds the custom domain.
Know the license
The source of Cloudy Reader is licensed under the Apache License 2.0. The LICENSE file in the repository root holds the terms.
The built .app also bundles GPL components, such as piper-tts and jdk4py, plus phonemizer-fork in a build with Kokoro. Distributing the .app is subject to their licenses. The third-party notices in the README list every bundled component.