BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2 或任何藍牙控制器,搭配 MyWhoosh、Rouvy、Zwift 等。iOS、Android、Windows、macOS 均可免費試用。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Score. Bet. Settle up.
An offline-first PWA for keeping a golf bet honest. Enter scores hole by hole,
pick a game, and the app tracks the money as you play — then shows exactly who
owes whom, in the fewest transactions, when you walk off 18.
Games: Wolf (with Hammer and Breakout), Nassau with presses, Skins, Match Play,
Stableford, Vegas, Snake, Bingo Bango Bongo, Sixes, Banker, and Trash/Dots.
There is no build step for the app. index.html is the whole thing — open it,
or serve the repository root over HTTP so the service worker registers:查看完整 README ↗Score. Bet. Settle up.
An offline-first PWA for keeping a golf bet honest. Enter scores hole by hole, pick a game, and the app tracks the money as you play — then shows exactly who owes whom, in the fewest transactions, when you walk off 18.
Games: Wolf (with Hammer and Breakout), Nassau with presses, Skins, Match Play, Stableford, Vegas, Snake, Bingo Bango Bongo, Sixes, Banker, and Trash/Dots.
The shipped app is one file, index.html — open it, or serve the repository
root over HTTP so the service worker registers:
python3 -m http.server 8000
index.html is generated. The source is under src/: the markup in
src/index.html, the stylesheet in src/css/, and the script in src/js/,
one file per area of the app (players, scoring, games, storage, screens, live
rounds, …). Edit those, then rebuild and commit both:
npm run build # reassemble index.html from src/ and data/courses.json
npm run format # optional: format src/ with Prettier (CI checks it)
The build only concatenates: each <style>/<script> in src/index.html
is filled with its folder's files in filename order, so the numeric prefixes
are the load order. Nothing is transformed, and the shipped code is exactly
the source. npm test fails if index.html is out of date.
Much of the script was recovered from minified code, so many functions still
have single-letter parameter names (function (e, t)). Rename them as you
touch them.
The money math is the product, so it has the most tests.
| Command | What it covers |
|---|---|
npm test | Money math, Wolf/Hammer settlement, the check that index.html is built from the current src/ and course data, and a static design-system audit. A few seconds, no dependencies. |
npm run test:flows | Drives a real browser (Playwright) through complete rounds — the only check that sees bugs in the seam between two screens. |
npm run test:rules | Runs firestore.rules through the real rules engine in the Firestore emulator. Requires Java. |
npm run test:live | Two real browsers sharing one round against the Firestore and Auth emulators and the real security rules — publishing, joining by code, scores and Breakouts crossing the wire, the watch link, and account deletion. Requires Java. |
All three run in CI on every push.
data/courses.json is the single source of truth for the built-in course list;
it is inlined into index.html at build time because the app reads COURSE_DB
synchronously and has to work with no signal. After editing it, run
npm run build and commit both files — npm test fails if they drift.
See data/README.md for the schema and validation rules.
Diagrams of the system, the money engine, live rounds, and CI live in
docs/architecture.md.
index.html — the entire application, built from src/: markup,
styles, and logic in one file, so the service worker caches one shell and the app opens on a course
with no signal. Nothing on the launch path goes to the network. The Firebase
SDK in particular is not a blocking <script src>: it is listed in
FIREBASE_SDK and fetched by ensureFirebase() on idle, after the first
screen is up — as three blocking tags it put 528KB of uncacheable
cross-origin JavaScript in front of the scorecard, and in a dead zone the app
never opened at all. Anything that genuinely needs the SDK (sign-in, joining
a live round, a ?watch= link) awaits ensureFirebase().sw.js — the service worker. Caches the shell and static assets
individually (never addAll, which fails atomically) and announces updates
through the cache so a reloaded page still sees them.src/ — the readable source index.html is built from.data/, scripts/ — the course database, build.js (which assembles
index.html), and the preview builds the browser tests use.test/ — money math, Wolf/Hammer settlement, security rules, browser
flows, and a static design-system audit.Local storage is the source of truth. Everything in Firestore is a mirror of what is already on the device, which is why a signed-out player loses nothing.
scripts/build-preview.py produces the two builds the browser tests drive. Both
work by rewriting the one FIREBASE_SDK array — emptied for flows.mjs, so
ensureFirebase() resolves to "no Firebase" without a request; repointed at
node_modules for live-round.test.mjs, which then drives the real SDK against
the emulators. Rewriting the list rather than deleting the loader keeps the
loader, its retry, and every await ensureFirebase() call site under test.
Google Sign-In (Firebase Auth) is optional and unlocks cross-device sync, live rounds, and the shared course library. Three Firestore collections:
| Collection | Contents | Who can read it |
|---|---|---|
users/{uid} | A private mirror of one player's profiles, courses, and round history | That player only |
liveRounds/{code} | A round in progress. The document ID is the six-character share code. | Anyone holding the code |
sharedCourses/{slug} | The community course library — course data only, no personal information | Any signed-in player |
The share code being the document ID is load-bearing. A round can be fetched by its code and the collection can never be listed by anyone but its own owner, so there is no query that returns rounds you were not invited to.
firestore.rules is the deployed policy, not a copy of it: it is tested in CI
(test/rules.test.mjs, 34 cases against the real rules engine) and deployed
from .github/workflows/deploy-rules.yml on every push to main that touches
it. Editing rules in the Firebase console is how the repository and production
drift apart — don't.
Deploying requires a FIREBASE_SERVICE_ACCOUNT repository secret (a service
account JSON key with the Firebase Rules Admin role). Without it the workflow
still tests the rules and warns instead of deploying.
privacy.html and terms.html are served from
the site root so they have stable public URLs — app stores require a reachable
privacy policy link, and a policy that only exists inside a modal has no URL.
The in-app Settings → Legal screens summarise the same text.LICENSE — proprietary, all rights reserved.Before publishing: both policy pages carry a CONTACT EMAIL placeholder and
name "The Press" rather than a legal entity. Replace both with the real contact
address and the company's registered name once it exists.
The Press is a scorekeeper and a calculator. It never accepts, holds, or transfers money — the settle-up buttons hand off to Venmo, Cash App, or PayPal with an amount pre-filled, and the payment happens between the players in those apps. Keeping it that way is what keeps this a utility rather than a regulated money transmitter or gambling operator.