Cloudy Reader

Reads National Science Bowl questions aloud on your Mac.

View project on GitHub

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

  1. Set up a checkout. See Build the app.
  2. Create a branch from the current main.
  3. Make small commits. Each commit does one thing and leaves the tests green.
  4. Run the gate before you push:

    make ci
    

    The gate needs shellcheck, prettier, and markdownlint on your PATH. See Test and CI.

  5. Push the branch and open a pull request against main.
  6. Wait for the check ci. Fix the failures and push again. A new push cancels the older run.
  7. A maintainer merges with a rebase merge. Each of your commits lands on main as 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, or docs.
  • Add a scope in parentheses when it helps. Examples: gui, app, and parser.
  • 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, and UP. Run make fmt to 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 NaHCO3 staying “N A H C O three” guards this rule.
  • Shared types. cloudyreader/models.py holds 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 that uv.lock names the public index.
  • Generated files. Never commit .venv/, .data/, .cache/, dist/, or build/. The .gitignore file lists them.

Change the version

The version lives in three places. Change all three together:

  • version in pyproject.toml. The bundle’s Info.plist and the package metadata read it.
  • __version__ in cloudyreader/__init__.py. The HTTP User-Agent of lookups uses it.
  • version in the cloudyreader entry of uv.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_group is Manual or Developer. nav_order sorts 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 -light file name. For a window snapshot, add frame="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.py before 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.

Cloudy Reader is an independent project. It is not affiliated with, endorsed by, or sponsored by the U.S. Department of Energy or the National Science Bowl.

The source code is licensed under Apache-2.0.