# Contributing to Tidebreak Thanks for your interest in Tidebreak! It's early — the fastest way to help is to try the latest build, file focused issues, and discuss design before large changes. ## Ground rules - **Open an issue before a large PR.** For anything beyond a small fix, let's align on the approach first so your time is well spent. - **No secrets, ever.** Never commit credentials, tokens, or `.env` files. CI runs a secret scan on every push and pull request. ## Development Local voice transcription runs in a separately published `tidebreak-whisper` helper the desktop downloads on demand, so building the desktop app itself no longer requires CMake. The helper is its own Cargo workspace, excluded from the root one so that no `--workspace` command compiles whisper.cpp. Building it (`cargo build --release --manifest-path crates/tidebreak-whisper/Cargo.toml`) does need CMake, and so does the `Publish whisper helper` workflow. ```sh # Run the desktop app: installs the UI dependencies, then opens the window. # Arguments are forwarded to `cargo tauri dev`. scripts/dev.sh # Run Storybook for the desktop UI: installs the UI dependencies, then starts # the Storybook dev server. Arguments are forwarded to `pnpm storybook`. scripts/storybook.sh # Drive the real UI against a scripted debug server in Chromium (needs # Docker). Arguments are forwarded to `playwright test`. scripts/e2e.sh # Build everything cargo build --workspace # Reclaim generated artifacts from inactive worktrees (reports first) scripts/clean-worktree-artifacts.sh scripts/clean-worktree-artifacts.sh --worktree ../finished-task --yes # Wipe this machine's debug-app profile so the next `scripts/dev.sh` run # starts from empty stores. Does not touch an installed release profile. scripts/wipe-dev.sh scripts/wipe-dev.sh --yes # Formatting, lints, and tests (what CI runs) cargo fmt --all --check cargo clippy --workspace --all-targets -- -D warnings cargo test --workspace # Without --write, biome format checks and does not rewrite. pnpm --dir crates/tidebreak-desktop/ui exec biome format src pnpm --dir crates/tidebreak-desktop/ui lint pnpm --dir crates/tidebreak-desktop/ui test # Optional fast checks for staged files git config core.hooksPath .githooks ``` The Rust toolchain is pinned to an exact version in [`rust-toolchain.toml`](rust-toolchain.toml); `rustup` will pick it up automatically. Bump the pin deliberately — a floating `stable` would let a rustup update fail the tree for everyone at once under clippy `-D warnings`. ### macOS keychain prompts Tidebreak keeps secrets in the login keychain, and macOS ties keychain approvals to the binary's code signature — so unsigned dev builds would re-trigger the access prompt on every rebuild. To prevent that, `cargo run` and `cargo test` launch through [`scripts/macos-dev-sign-runner.sh`](scripts/macos-dev-sign-runner.sh), which signs the binary before running it. **What makes an approval stick is a team identifier.** macOS records the approval as a requirement it can re-evaluate — the code-signing identifier plus the certificate's team — and any later build that satisfies it is let through. A certificate without a team identifier gives it nothing stable to match, so the approval is pinned to that one binary's cdhash and the next rebuild prompts again. **Always Allow** cannot settle it either, and the partition-list repair Apple documents also needs a team identifier. So the runner signs with the first of these it finds: 1. `$TIDEBREAK_DEV_SIGNING_IDENTITY`, if set — set it empty to opt out 2. an **Apple Development** identity 3. a **Developer ID Application** identity 4. a `tidebreak-dev` certificate already in a searchable keychain 5. a local-only `tidebreak-dev` identity it creates in a dedicated keychain under `~/Library/Application Support/Tidebreak/dev-signing` Options 2 and 3 carry a team identifier and are the ones that stop the prompts; 3 means local development signs with a distribution key, which is the price of 2 not being available. Option 5 needs no Apple account and unlocks with its own generated password, so it never asks for your login-keychain password — but being self-signed it has no team identifier, so credential prompts will keep returning on rebuild. It is a floor, not a fix. Every binary is signed with the same fixed identifier (`tidebreak-dev`), so one approval covers every dev binary — including test executables, whose hashed file names change between builds. #### Credentials stored under a different identity An item is bound to whatever signed the binary that **created** it. Credentials stored before dev signing existed — or under an identity you have since moved away from, including the self-signed fallback — keep prompting. Re-home them once, which rewrites each item under the identity in use now: ```sh cargo run -p tidebreak-cli -- rehome-secrets ``` Run it through Cargo so the signing runner applies. Each credential asks for access once or twice more while it is read and its old item removed — plain **Allow** is enough — and then stops, including after later rebuilds, provided the identity carries a team identifier. `security find-generic-password -s tidebreak.dev` lists what the dev profile has stored. The first build after switching identities may also raise a one-time *codesign wants to access key* prompt for the new signing key. **Always Allow** does settle that one — `codesign` is a stable Apple-signed binary — or set the key's partition list to avoid it up front. ### Desktop UI See [`crates/tidebreak-desktop/README.md`](crates/tidebreak-desktop/README.md). Short version: `cd crates/tidebreak-desktop && pnpm --dir ui install && cargo tauri dev`, or run the React UI in a browser against `tidebreak serve` via `ui/.env.local`. Start that `serve` with `TIDEBREAK_DATA_DIR` set: without it, `serve` uses the dev app's data and cannot run beside the dev app. ### End-to-end flows To run the Playwright flows the CI `end-to-end` lane runs, use one command: ```sh scripts/e2e.sh ``` It builds the debug server with the self-host features and the desktop renderer, then boots a fresh machine for each flow in `e2e/tests`. Each machine serves the renderer from its own origin, the way a hosted machine does ([decision 82](docs/decisions/0082-the-hosted-machine-serves-the-renderer.md)), and plays the scripted provider and coding engine instead of a model, so no flow reaches the network. PostgreSQL and an S3 gateway run in throwaway Docker containers that the run removes; a run that was killed leaves them behind, and the next run removes them. Add `--headed` to watch the browser, or name a spec to run one flow. A failed flow leaves its Playwright trace and the server's logs in `e2e/test-results`. A flow fails when the page reaches off this computer or gets a 5xx from the machine. To tolerate a server error while an issue tracks it, list it under that issue with `test.use({ knownServerErrors })` in the flow, and delete the entry when the issue is fixed. In CI the lane drives the debug server that the `self-host server build` job uploads, which GitHub keeps for one day. To re-run the lane on a run older than that, use **Re-run all jobs**, which builds the server again; re-running the lane alone finds no server. ## Commit and PR conventions - Pull request titles must use a [Conventional Commits](https://www.conventionalcommits.org/) header. CI checks the title because squash merge makes it the release commit on `main`: `type(optional-scope)[!]: description`. - Allowed types are `feat`, `improve`, `fix`, `perf`, `deps`, `revert`, `docs`, `refactor`, `chore`, `build`, `ci`, and `test`. Use `improve` for user-visible polish or behavior changes that add no capability and correct no defect. Use `!` only with a release-driving type (`feat`, `improve`, `fix`, `perf`, `deps`, or `revert`) and only for an intentionally breaking product, API, data, or configuration change. - `feat` drives a minor release; `improve`, `fix`, `perf`, shipped `deps`, and `revert` changes drive a patch; maintenance-only types do not release. Before 1.0, breaking changes drive a minor release. See the [release guide](docs/releases.md) for the full policy and the deliberate `1.0.0` procedure. - Individual commits on a PR may use the same convention, but only the PR title becomes the squash commit used for release calculation. - Write a clear PR description: what changed and why. ## Merging Every pull request to `main` needs an approving review from `@brightwave-inc/engineering`. Members of that team can merge without waiting for the review. Squash-merge when the required checks pass. ## License By submitting a contribution, you agree that your contribution is provided under the [Apache License 2.0](LICENSE) and that you have the right to submit it. There is no separate CLA and no CLA check on pull requests. ## Code of Conduct This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By participating, you agree to uphold it.