From 9c39af718b43dea50b08454ca1b0ab76d3f84688 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 13:06:35 +0000 Subject: [PATCH 1/6] docs: add CONTRIBUTING.md with proof-of-testing rule for new contributors --- CONTRIBUTING.md | 317 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..a2196570d9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,317 @@ +# Contributing to Amethyst + +Thanks for your interest in improving Amethyst. This document captures the +expectations, conventions, and review rules for code, documentation, and +translation contributions across all modules in this repository (`amethyst/`, +`desktopApp/`, `quartz/`, `commons/`, `cli/`, `quic/`, `nestsClient/`). + +By contributing, you agree to license your work under the MIT license. Any +work contributed where you are not the original author must contain its +license header with the original author(s) and source. + +- [Ways to contribute](#ways-to-contribute) +- [Human and AI contributions](#human-and-ai-contributions) +- [New contributor: proof of testing](#new-contributor-proof-of-testing) +- [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features) +- [Security issues](#security-issues) +- [Development setup](#development-setup) +- [Project layout](#project-layout) +- [Where code belongs (sharing philosophy)](#where-code-belongs-sharing-philosophy) +- [Workflow](#workflow) +- [Coding standards](#coding-standards) +- [Tests](#tests) +- [Commits](#commits) +- [Pull requests](#pull-requests) +- [Translations](#translations) +- [Releases](#releases) + +--- + +## Ways to contribute + +- Fix bugs or implement features (see issues and the bounty notes in the + issue templates). +- Improve documentation in `README.md`, `BUILDING.md`, `SECURITY.md`, the + per-module `plans/` folders, and the `.claude/` skill docs. +- Add or improve translations on + [Crowdin](https://crowdin.com/project/amethyst-social). +- Report bugs, suggest features, or open issues at + [github.com/vitorpamplona/amethyst/issues](https://github.com/vitorpamplona/amethyst/issues) + or the mirror at + [gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst). +- Send patches over Nostr using + [GitStr](https://github.com/fiatjaf/gitstr) — see the address at the bottom + of the README. + +## Human and AI contributions + +We accept pull requests authored by humans and pull requests authored with +help from AI coding assistants (Claude Code, Copilot, Cursor, Codex, etc.). +The rules are the same in both cases: + +- **You are the author of record.** You are responsible for understanding, + testing, and defending every line of code in the PR — including code an + assistant wrote for you. "The AI did it" is not a valid response to review + feedback. +- **No hallucinated APIs or imports.** Don't submit code that calls functions, + classes, or libraries that don't exist in this repo or in our declared + dependencies. Build and run it before you push. +- **Disclose AI involvement if it's substantial.** If an assistant generated + most of the diff, a one-line note in the PR description is appreciated + (e.g. "Drafted with Claude Code, manually reviewed and tested"). This is a + courtesy, not a gate. +- **No machine-translated locale files.** Translations go through Crowdin so + native speakers can review them. + +## New contributor: proof of testing + +If this is your **first PR** to this repository, the PR description must +include proof that you actually ran the change. This applies whether the +patch was written by you or by an AI assistant. + +What "proof" means: + +- **Code/logic changes:** paste the output of the relevant test run, the CLI + command you ran, or the log lines that show the new behavior firing. Tests + passing in CI is necessary but not sufficient — show the new path + executing. +- **UI changes (any change to `amethyst/`, `desktopApp/`, or `@Composable` + code in `commons/`):** attach screenshots or a short screen recording of + the new or changed screen on a real device or emulator. Include: + - Light **and** dark themes when the change is visible in both. + - The empty / loading / error states if your change introduces them. + - For Android: device model + Android version (e.g. "Pixel 7, Android 14"). + - For Desktop: OS + window size (e.g. "macOS 14, 1440×900"). +- **Build / Gradle / packaging changes:** paste the `./gradlew` command and + the tail of its output, or the produced artifact name from + `desktopApp/build/compose/binaries/`. +- **Translation-only PRs:** screenshots are not required, but mention which + locale and which strings you touched. + +You don't need to keep doing this on every subsequent PR — once we've seen +that you can run the project end-to-end, a short test plan in the PR +description is enough. Maintainers may still ask for screenshots on visual +changes regardless of contributor seniority. + +If you cannot run a particular target locally (e.g. you don't have macOS but +your change affects the DMG build), say so explicitly in the PR — don't +claim it works when you haven't checked. Honest "I couldn't test this on +Windows" is far better than a silent guess. + +## Reporting bugs and requesting features + +Use the GitHub issue templates: + +- **Bug report** — include device, OS version, app version, app flavour + (Play / F-Droid / Desktop), and (if you sign with Amber) the Amber version. +- **Feature request** — describe the user-facing outcome you want. + +Both templates include a bounty field. Bounties are entirely optional, but +issues with bounties are typically picked up faster — see the templates for +context. + +## Security issues + +**Do not file security vulnerabilities as public GitHub issues.** Use +[GitHub private vulnerability reporting](https://github.com/vitorpamplona/amethyst/security/advisories/new) +instead. See [SECURITY.md](SECURITY.md) for scope, expected response times, +and the disclosure policy. + +## Development setup + +Prerequisites: + +1. **JDK 21+** (Zulu or Temurin recommended) +2. **Android Studio** (for Android development) +3. **Android 8.0+ phone or emulator** (for installing the Android app) +4. **Xcode + iOS simulator** if you're touching `quartz/` iOS code +5. **libsodium** for full local builds (`brew install libsodium` on macOS) + +Desktop packaging prerequisites (only if you're building installers) are +listed in [BUILDING.md § Prerequisites](BUILDING.md#prerequisites). + +Clone and import: + +```bash +git clone https://github.com/vitorpamplona/amethyst.git +cd amethyst +``` + +Common Gradle entry points: + +```bash +# Run desktop app +./gradlew :desktopApp:run + +# Install Android debug build +./gradlew :amethyst:installDebug +# or specifically: +./gradlew installFdroidDebug +./gradlew installPlayDebug + +# Build Quartz for all targets +./gradlew :quartz:build + +# Full build (compiles all modules + runs tests) +./gradlew build +``` + +## Project layout + +``` +quartz/ Nostr KMP library — protocol, crypto, models. No UI. +commons/ Shared Compose Multiplatform UI, icons, ViewModels, flows. +quic/ Pure-Kotlin QUIC v1 + HTTP/3 + WebTransport transport library. +nestsClient/ Audio-rooms client (NIP-53) built on :quic and :quartz. +amethyst/ Android app — Activity, layouts, navigation. +desktopApp/ Desktop JVM app — Window, sidebar, keyboard shortcuts. +cli/ `amy` — non-interactive JVM CLI over quartz + commons. +ammolite/ Legacy support module. +``` + +Per-module design docs live in `/plans/YYYY-MM-DD-.md`. The +global `docs/plans/` folder is frozen — don't add new files there. + +## Where code belongs (sharing philosophy) + +Before writing a new class or composable, check whether it already exists. +Most logic is already implemented somewhere in the tree — duplicating it is +the #1 cause of PR churn. + +Place new code by purpose: + +| What you're adding | Goes in | +|---|---| +| Nostr event types, NIPs, tags, signing, crypto, Bech32 | `quartz/commonMain/` | +| Shared Composables, icons, ViewModels, StateFlows | `commons/commonMain/viewmodels/` or `commons/commonMain/` | +| Android-only screen, navigation, system integration | `amethyst/` | +| Desktop-only window, sidebar, menu bar, shortcut | `desktopApp/` | +| `amy ` subcommand (thin assembly only) | `cli/src/main/kotlin/.../cli/` | +| QUIC / HTTP/3 / WebTransport protocol code | `quic/` | +| MoQ session / audio-room logic | `nestsClient/` | + +Hard rules: + +- `quartz/` has **no UI**. +- `cli/` has **no Nostr protocol or business logic** — it's a thin assembly + layer over `quartz` + `commons`. If your CLI command needs new behavior, + extract that behavior into `commons/` first. +- ViewModels are platform-agnostic and belong in `commons/commonMain/`. Only + screens (the Composable that wires layout + navigation) stay in the + platform module. +- For platform-specific behavior in a shared file, use `expect`/`actual` — + ask `/kotlin-multiplatform` if unsure. + +## Workflow + +1. **Survey first.** Search the codebase for existing implementations before + writing new code. The `.claude/CLAUDE.md` file has a recommended set of + `grep` queries; running them takes a minute and routinely turns up the + exact class you were about to re-invent. +2. **Open a small PR.** One feature or one fix per PR. Refactors that + support the change are fine; unrelated refactors are not. +3. **Branch off `main`.** +4. **Run tests + spotless locally** before pushing (see below). +5. **Open the PR** with a description that explains *why*, not just *what*, + and includes the proof-of-testing notes described above. + +## Coding standards + +- Kotlin, formatted with Spotless. Always run: + ```bash + ./gradlew spotlessApply + ``` + before considering a task complete. CI runs `./gradlew spotlessCheck` and + will fail on unformatted code. +- Never use `--no-verify` to bypass pre-commit hooks. If a hook fails, + fix the cause. +- Don't add features, refactor, or introduce abstractions beyond what the + task requires. A bug fix doesn't need surrounding cleanup. +- Don't add error handling, fallbacks, or validation for scenarios that + can't happen. Validate at system boundaries (user input, external APIs). +- Prefer editing existing files to creating new ones. Don't create + documentation files (`*.md`) unless asked or unless the change genuinely + warrants one. +- Default to writing no comments. Only add a comment when the *why* is + non-obvious (a workaround, a subtle invariant, behavior that would + surprise a reader). Don't restate what the code does. +- Don't reference the current task, fix, or callers in comments + (e.g. "added for issue #123") — that belongs in the PR description. + +## Tests + +```bash +# Unit + KMP common tests across all modules +./gradlew test + +# Android instrumented tests (needs device or emulator) +./gradlew connectedAndroidTest + +# A single module +./gradlew :quartz:test +./gradlew :commons:test +``` + +Add tests when: + +- You fix a bug — a regression test that fails before your fix and passes + after. +- You add new protocol code in `quartz/` — Nostr event parsing, NIP + implementations, and crypto paths should have unit coverage. +- You add anything to `commons/` that has non-trivial state transitions. + +UI changes don't need automated UI tests, but they do need the screenshots +described in the proof-of-testing section. + +## Commits + +- Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, + `fix:`, `refactor:`, `docs:`, `chore:`, `test:`, etc. +- One logical change per commit. Squashing during PR review is fine. +- Keep subject lines under ~72 characters. Use the body to explain *why* + the change was made when it isn't obvious. +- Branch names follow `feat/-` or + `fix/-` (e.g. `feat/desktop-sidebar-resize`, + `fix/android-notification-leak`). + +## Pull requests + +A good PR description has: + +1. **Summary** — 1–3 sentences on what changed and why. +2. **Modules touched** — `quartz`, `commons`, `amethyst`, `desktopApp`, + `cli`, etc. +3. **Test plan** — exactly what you ran, on what platform, with what + result. Include screenshots / recordings for UI changes (mandatory for + first-time contributors; expected for all contributors on visual + changes). +4. **Risk / rollback** — if the change touches relay subscriptions, the + `NostrClient`, signing, or anything in `Account`/`LocalCache`, call out + what could break and how to revert. + +We aim to give first-pass feedback within a few days. PRs may sit longer if +they're large, touch security-sensitive code, or arrive during a release +window. + +## Translations + +- Submit translations via + [Crowdin](https://crowdin.com/project/amethyst-social), not as direct PRs + to `strings.xml`. Crowdin pushes are integrated through the + `crowdin.yml` workflow. +- The `/find-missing-translations` skill can help reviewers spot + untranslated keys for a target locale. + +## Releases + +Release runbooks (Android AAB upload, desktop packaging on macOS / Windows / +Linux, Homebrew cask, Winget manifest, Apple Developer signing, asset +naming) live in [BUILDING.md § Release runbook](BUILDING.md#release-runbook) +and [BUILDING.md § Bootstrap runbook](BUILDING.md#bootstrap-runbook-one-time). + +If your PR needs to be cut into a particular release, mention it in the PR +description and the maintainers will coordinate. + +--- + +Thanks again for contributing. From ee20ab6ca47e03d11e98c7d27c17197d7a8e7baf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 13:25:13 +0000 Subject: [PATCH 2/6] docs(contributing): document interop suites and issue-template practice --- CONTRIBUTING.md | 103 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2196570d9..4fde7fd57f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,7 @@ license header with the original author(s) and source. - [Workflow](#workflow) - [Coding standards](#coding-standards) - [Tests](#tests) +- [Interoperability tests](#interoperability-tests) - [Commits](#commits) - [Pull requests](#pull-requests) - [Translations](#translations) @@ -100,15 +101,63 @@ Windows" is far better than a silent guess. ## Reporting bugs and requesting features -Use the GitHub issue templates: +We use GitHub issue templates at +[`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/). Please use them rather +than opening a blank issue — they exist so triage can route the report +without a follow-up round trip. -- **Bug report** — include device, OS version, app version, app flavour - (Play / F-Droid / Desktop), and (if you sign with Amber) the Amber version. -- **Feature request** — describe the user-facing outcome you want. +### Bug report (`[BUG]` title prefix) -Both templates include a bounty field. Bounties are entirely optional, but -issues with bounties are typically picked up faster — see the templates for -context. +Required fields: + +- **Describe the bug** — one-paragraph summary of what's wrong. +- **To Reproduce** — numbered steps (1, 2, 3, …) that walk a maintainer from + a fresh app launch to the failure. "It crashes sometimes" is not a repro; + "tap +, paste this nevent, tap Send" is. +- **Expected behavior** — what should have happened. +- **Video and Screenshots** — attach them whenever the bug is visual, + involves navigation, or is timing-sensitive. +- **Device info** — Phone Brand/Model, Android version, App version, App + flavour (Google Play / F-Droid / Desktop), and Amber version if you sign + with NIP-55. +- **Bounty (in Bitcoin sats)** — see below. + +### Feature request (`[FEATURE]` title prefix) + +Required fields: + +- **Describe the solution you'd like** — what the user-facing outcome is. + Skip implementation suggestions unless they're load-bearing. +- **Bounty (in Bitcoin sats)** — see below. + +### Bounties + +Both templates include a bounty field, and the practice is unusually +literal in this project: **maintainer time is allocated by bounty size**. +The templates say so plainly — quoting them: + +> The size of the bounty is proportional to how much this matters to you. If +> no bounty is offered, not even a small one, this bug will not be worked +> on because it doesn't matter to you. + +In practice: + +- Bug fixes and features are prioritized roughly by bounty ÷ effort. +- Even a small bounty (a few hundred sats) outranks a zero-bounty issue. +- Bounties are not refunded if you change your mind, so size them + honestly against how much you want the outcome. +- If you can fix the issue yourself, you don't need a bounty — open the + PR. Bounties exist to move issues that **nobody is currently working on**. +- Security issues do not need a bounty and should not be filed as public + issues — see [SECURITY.md](SECURITY.md). + +### Sending issues over Nostr + +The repository is also mirrored at +[`gitworkshop.dev/repo/amethyst`](https://gitworkshop.dev/repo/amethyst) for +Nostr-native issue tracking, and patches can be sent via +[GitStr](https://github.com/fiatjaf/gitstr) to the address at the bottom of +the README. ## Security issues @@ -263,7 +312,45 @@ Add tests when: UI changes don't need automated UI tests, but they do need the screenshots described in the proof-of-testing section. -## Commits +## Interoperability tests + +Amethyst ships several cross-stack interop harnesses that drive our code +against external reference implementations. They are **not run in CI** — +they're slow, require Rust / bun / Docker / Chromium, and most PRs don't +touch the code they cover. If your change *does* touch a covered area, you +are expected to run the relevant suite locally and paste the result into +the PR description. Reviewers may ask if you didn't. + +| Suite | Path | What it covers | When you must run it | +|---|---|---|---| +| **Marmot / MLS (Whitenoise)** | `cli/tests/marmot/` | NIP-EE MLS groups: KeyPackage publish, group create/invite/remove, admin promote/demote, leave, replay, KP rotation — Amethyst (or `amy`) ↔ `whitenoise-rs` Rust reference | Changes to MLS / Marmot code in `quartz/` or `commons/`, or to the Marmot UI / group-chat flow in `amethyst/` | +| **NIP-17 DMs (amy ↔ amy)** | `cli/tests/dm/` | Text + file DM round-trip, strict kind:10050, `--allow-fallback` NIP-65 chain, `dm list --since` window slide | Changes to DM, gift-wrap (NIP-59), or NIP-17 code paths in `quartz/` or `commons/` | +| **Audio rooms (manual)** | `cli/tests/nests/` | 47-test manual harness: Amethyst Android ↔ nostrnests.com reference web client. Host/listener flows, hand-raise, role promotion, kicks, schedule, reconnect, JWT refresh, PIP | Changes to NIP-53 audio rooms (`amethyst/` UI) or `nestsClient/` that affect host/listener UX | +| **MoQ-lite hang-tier** | `nestsClient/tests/hang-interop/` | Rust `hang-listen` / `hang-publish` ↔ Amethyst Kotlin through a real `moq-relay` 0.10.x subprocess. Wire-byte capture, FFT-on-PCM, mute / hot-swap / packet-loss / late-join / 60s broadcast / multi-listener fan-out | Changes to `nestsClient/.../moq/lite/`, `nestsClient/.../audio/`, `MoqLite*Speaker.kt` / `*Listener.kt`, `ReconnectingNests*.kt`, or `quartz/.../nip53` | +| **MoQ-lite browser-tier** | `nestsClient/tests/browser-interop/` | Headless Chromium with `@moq/lite` + `@moq/hang` via Playwright ↔ Amethyst Kotlin (forward + reverse). WebCodecs encode/decode, ALPN negotiation, browser-side reconnect | Same as hang-tier, plus any change to `:quic` (WebTransport, packet header protection, key updates, stream demux) | +| **QUIC interop-runner** | `quic/interop/` | The standard `quic-interop-runner` matrix (ns-3 sim) against aioquic, picoquic, quic-go, quinn. Handshake, transfer, loss, corruption, IPv6, migration, key update, version negotiation | Any change in `:quic` that could affect wire bytes, congestion control, or the TLS state machine | + +### Running them + +Each suite has its own README with prerequisites and flags. Quick links: + +- **Marmot, DM, audio-rooms (CLI):** [`cli/tests/README.md`](cli/tests/README.md). + Headless variants require only `cargo` + a loopback `nostr-rs-relay`; + the interactive Marmot variant additionally prompts the human to drive + the Amethyst Android UI. +- **MoQ-lite hang + browser:** [`nestsClient/tests/README.md`](nestsClient/tests/README.md). + Opt-in via `-DnestsHangInterop=true` and/or + `-DnestsBrowserInterop=true` on `:nestsClient:jvmTest`. Cold first run is + ~10–13 min per tier; cached runs ~3–7 min. +- **QUIC interop-runner:** [`quic/interop/`](quic/interop/) plus its plan + at `quic/interop/plans/2026-05-06-interop-runner.md`. The standard sweep + is `for peer in aioquic picoquic quic-go quinn; do + quic/interop/run-matrix.sh -s $peer; done`. Always sequential — + `run-matrix.sh` is not safe to invoke concurrently. + +If a change is documentation-only, UI-only, build-script-only, or otherwise +cannot affect wire bytes / decoded audio / MLS state / DM envelopes, skip +the interop suites and say so in the PR description. - Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, `test:`, etc. From cfa6899c65e80fdc36c120b0ecd05c8bf613eed6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 13:42:22 +0000 Subject: [PATCH 3/6] docs: cross-link CONTRIBUTING.md from README and add PR template --- .github/PULL_REQUEST_TEMPLATE.md | 85 ++++++++++++++++++++++++++++++++ README.md | 17 +++++-- 2 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..7decd1b915 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,85 @@ + + +## Summary + + + +## Modules touched + + + +- [ ] `amethyst/` (Android app) +- [ ] `desktopApp/` (Desktop JVM app) +- [ ] `quartz/` (Nostr KMP library) +- [ ] `commons/` (shared UI / ViewModels) +- [ ] `cli/` (`amy`) +- [ ] `quic/` (QUIC + HTTP/3 + WebTransport) +- [ ] `nestsClient/` (audio rooms / MoQ) +- [ ] Build / Gradle / CI +- [ ] Docs only + +## Test plan + + + +- [ ] Ran `./gradlew spotlessApply` — repo is formatted +- [ ] Ran `./gradlew test` (or the relevant module's tests) +- [ ] Manually exercised the change (see notes below) + +Notes / screenshots: + + + +## Interop suites + + + +- [ ] N/A — change can't affect wire bytes / decoded audio / MLS state / DM envelopes +- [ ] Marmot / MLS — `cli/tests/marmot/marmot-interop-headless.sh` (NIP-EE / `whitenoise-rs`) +- [ ] NIP-17 DM — `cli/tests/dm/dm-interop-headless.sh` +- [ ] Audio rooms manual — `cli/tests/nests/nests-interop.sh` (Amethyst ↔ nostrnests.com) +- [ ] MoQ-lite hang-tier — `:nestsClient:jvmTest -DnestsHangInterop=true` +- [ ] MoQ-lite browser-tier — `:nestsClient:jvmTest -DnestsBrowserInterop=true` +- [ ] QUIC interop-runner — `quic/interop/run-matrix.sh -s {aioquic,picoquic,quic-go,quinn}` + +## Risk / rollback + + + +## AI assistance + + + +- [ ] Drafted with AI assistance, manually reviewed and tested +- [ ] Written by hand + +## License + +- [ ] By submitting this PR, I agree to license my contribution under the + MIT license. Any code I did not author personally carries its + original license header. diff --git a/README.md b/README.md index bd5cc3036b..340a81bf75 100644 --- a/README.md +++ b/README.md @@ -440,11 +440,22 @@ to `onPause` methods. ## Contributing -Issues can be logged on: [https://gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst) +See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full guide — workflow, +coding standards, the proof-of-testing rule for first-time contributors +(human or AI-assisted), the cross-stack interop suites, and how the +`[BUG]` / `[FEATURE]` issue templates and bounty system work. -[GitHub issues](https://github.com/vitorpamplona/amethyst/issues) and [pull requests](https://github.com/vitorpamplona/amethyst/pulls) here are also welcome. Translations can be provided via [Crowdin](https://crowdin.com/project/amethyst-social) +Quick links: -You can also send patches through Nostr using [GitStr](https://github.com/fiatjaf/gitstr) to [this nostr address](https://patch34.pages.dev/naddr1qqyxzmt9w358jum5qyg8v6t5daezumn0wd68yvfwvdhk6qg7waehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2ap0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qgcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqxpqqqpmej720gac) +- [GitHub issues](https://github.com/vitorpamplona/amethyst/issues) and + [pull requests](https://github.com/vitorpamplona/amethyst/pulls). +- Nostr-native issue tracker: + [gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst). +- Translations: [Crowdin](https://crowdin.com/project/amethyst-social). +- Patches over Nostr: [GitStr](https://github.com/fiatjaf/gitstr) to + [this nostr address](https://patch34.pages.dev/naddr1qqyxzmt9w358jum5qyg8v6t5daezumn0wd68yvfwvdhk6qg7waehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2ap0qy2hwumn8ghj7un9d3shjtnwdaehgu3wvfnj7q3qgcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqxpqqqpmej720gac). +- Security issues: [SECURITY.md](SECURITY.md) (do **not** file as public + GitHub issues). By contributing to this repository, you agree to license your work under the MIT license. Any work contributed where you are not the original author must contain its license header with the original author(s) and source. From 8ea7d1ee81d9acfdcab58994a625a07910701c4a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 14:11:59 +0000 Subject: [PATCH 4/6] docs(pr-template): drop modules-touched and risk/rollback sections --- .github/PULL_REQUEST_TEMPLATE.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7decd1b915..b3287d56d8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,20 +11,6 @@ Delete any section below that doesn't apply. -## Modules touched - - - -- [ ] `amethyst/` (Android app) -- [ ] `desktopApp/` (Desktop JVM app) -- [ ] `quartz/` (Nostr KMP library) -- [ ] `commons/` (shared UI / ViewModels) -- [ ] `cli/` (`amy`) -- [ ] `quic/` (QUIC + HTTP/3 + WebTransport) -- [ ] `nestsClient/` (audio rooms / MoQ) -- [ ] Build / Gradle / CI -- [ ] Docs only - ## Test plan - ## AI assistance @@ -20,8 +20,9 @@ For UI changes, attach screenshots (light + dark) or a short recording. For Android: device model + Android version. For Desktop: OS + window size. For build/packaging changes: paste the `./gradlew` command + tail of output. -If this is your FIRST PR to this repo, this section is required regardless -of how small the change is — see CONTRIBUTING.md § Proof of testing. --> +If you are NOT a regular contributor to this repo, this section is required +regardless of how small the change is — see CONTRIBUTING.md § Proof of +testing. --> - [ ] Ran `./gradlew spotlessApply` — repo is formatted - [ ] Ran `./gradlew test` (or the relevant module's tests) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4fde7fd57f..16ceb43f31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ license header with the original author(s) and source. - [Ways to contribute](#ways-to-contribute) - [Human and AI contributions](#human-and-ai-contributions) -- [New contributor: proof of testing](#new-contributor-proof-of-testing) +- [New / occasional contributor: proof of testing](#new--occasional-contributor-proof-of-testing) - [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features) - [Security issues](#security-issues) - [Development setup](#development-setup) @@ -64,11 +64,13 @@ The rules are the same in both cases: - **No machine-translated locale files.** Translations go through Crowdin so native speakers can review them. -## New contributor: proof of testing +## New / occasional contributor: proof of testing -If this is your **first PR** to this repository, the PR description must -include proof that you actually ran the change. This applies whether the -patch was written by you or by an AI assistant. +If you are **not a regular contributor** to this repository — that is, this +is one of your first PRs, or you've only contributed sporadically and the +current maintainers wouldn't recognize your handle from recent history — the +PR description must include proof that you actually ran the change. This +applies whether the patch was written by you or by an AI assistant. What "proof" means: @@ -89,10 +91,10 @@ What "proof" means: - **Translation-only PRs:** screenshots are not required, but mention which locale and which strings you touched. -You don't need to keep doing this on every subsequent PR — once we've seen -that you can run the project end-to-end, a short test plan in the PR -description is enough. Maintainers may still ask for screenshots on visual -changes regardless of contributor seniority. +Once you have an established track record in the repository, a short test +plan in the PR description is enough on subsequent PRs. Maintainers may +still ask for screenshots on visual changes regardless of contributor +seniority. If you cannot run a particular target locally (e.g. you don't have macOS but your change affects the DMG build), say so explicitly in the PR — don't @@ -370,7 +372,7 @@ A good PR description has: `cli`, etc. 3. **Test plan** — exactly what you ran, on what platform, with what result. Include screenshots / recordings for UI changes (mandatory for - first-time contributors; expected for all contributors on visual + non-regular contributors; expected for all contributors on visual changes). 4. **Risk / rollback** — if the change touches relay subscriptions, the `NostrClient`, signing, or anything in `Account`/`LocalCache`, call out diff --git a/README.md b/README.md index 340a81bf75..2327761cb3 100644 --- a/README.md +++ b/README.md @@ -441,8 +441,8 @@ to `onPause` methods. ## Contributing See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full guide — workflow, -coding standards, the proof-of-testing rule for first-time contributors -(human or AI-assisted), the cross-stack interop suites, and how the +coding standards, the proof-of-testing rule for new / occasional +contributors (human or AI-assisted), the cross-stack interop suites, and how the `[BUG]` / `[FEATURE]` issue templates and bounty system work. Quick links: From fc29147c4a7649ffbcd3ffba497263555fc473df Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 11 May 2026 15:56:12 +0000 Subject: [PATCH 6/6] docs(contributing): tighten and fix consistency bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add missing `## Commits` header so the ToC anchor resolves. - Drop "Modules touched" and "Risk / rollback" from the PR description checklist so CONTRIBUTING matches the PR template (PR template lost those sections in 8ea7d1e). - Fold "Human and AI contributions" into "Proof of testing" — the two sections restated the same author-of-record rule. - Remove duplicate "Sending issues over Nostr" subsection (already covered under "Ways to contribute"). - Compress "Project layout" ascii tree into a bullet list and merge with "Where code belongs". - Tighten "Coding standards" (drop bullets that restated generic don't-over-engineer guidance) and "Bounties" (single paragraph instead of a quote block plus six bullets). - Trim the interop "Running them" subsection to the non-obvious bits (opt-in flags, run-matrix.sh not concurrency-safe) instead of repeating paths from the table. Net: 406 → 316 lines, no information lost. --- CONTRIBUTING.md | 306 +++++++++++++++++------------------------------- 1 file changed, 108 insertions(+), 198 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 16ceb43f31..d955010bc0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,13 +10,11 @@ work contributed where you are not the original author must contain its license header with the original author(s) and source. - [Ways to contribute](#ways-to-contribute) -- [Human and AI contributions](#human-and-ai-contributions) -- [New / occasional contributor: proof of testing](#new--occasional-contributor-proof-of-testing) +- [Proof of testing (new / occasional contributors)](#proof-of-testing-new--occasional-contributors) - [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features) - [Security issues](#security-issues) - [Development setup](#development-setup) -- [Project layout](#project-layout) -- [Where code belongs (sharing philosophy)](#where-code-belongs-sharing-philosophy) +- [Where code belongs](#where-code-belongs) - [Workflow](#workflow) - [Coding standards](#coding-standards) - [Tests](#tests) @@ -38,135 +36,86 @@ license header with the original author(s) and source. [Crowdin](https://crowdin.com/project/amethyst-social). - Report bugs, suggest features, or open issues at [github.com/vitorpamplona/amethyst/issues](https://github.com/vitorpamplona/amethyst/issues) - or the mirror at + or the Nostr mirror at [gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst). - Send patches over Nostr using [GitStr](https://github.com/fiatjaf/gitstr) — see the address at the bottom of the README. -## Human and AI contributions +## Proof of testing (new / occasional contributors) We accept pull requests authored by humans and pull requests authored with -help from AI coding assistants (Claude Code, Copilot, Cursor, Codex, etc.). -The rules are the same in both cases: +help from AI coding assistants (Claude Code, Copilot, Cursor, Codex, etc.) +under the same rules: - **You are the author of record.** You are responsible for understanding, - testing, and defending every line of code in the PR — including code an - assistant wrote for you. "The AI did it" is not a valid response to review - feedback. -- **No hallucinated APIs or imports.** Don't submit code that calls functions, - classes, or libraries that don't exist in this repo or in our declared - dependencies. Build and run it before you push. -- **Disclose AI involvement if it's substantial.** If an assistant generated - most of the diff, a one-line note in the PR description is appreciated - (e.g. "Drafted with Claude Code, manually reviewed and tested"). This is a - courtesy, not a gate. + testing, and defending every line — including code an assistant wrote for + you. "The AI did it" is not a valid response to review feedback. +- **No hallucinated APIs or imports.** Don't submit code that calls + functions, classes, or libraries that don't exist in this repo or its + declared dependencies. Build and run it before you push. - **No machine-translated locale files.** Translations go through Crowdin so native speakers can review them. +- **Disclose substantial AI involvement** with a one-line note in the PR + description. Courtesy, not a gate. -## New / occasional contributor: proof of testing +If you are **not a regular contributor** to this repository — first PR, or +sporadic enough that maintainers wouldn't recognize your handle — the PR +description must include proof that you actually ran the change: -If you are **not a regular contributor** to this repository — that is, this -is one of your first PRs, or you've only contributed sporadically and the -current maintainers wouldn't recognize your handle from recent history — the -PR description must include proof that you actually ran the change. This -applies whether the patch was written by you or by an AI assistant. - -What "proof" means: - -- **Code/logic changes:** paste the output of the relevant test run, the CLI - command you ran, or the log lines that show the new behavior firing. Tests - passing in CI is necessary but not sufficient — show the new path - executing. -- **UI changes (any change to `amethyst/`, `desktopApp/`, or `@Composable` - code in `commons/`):** attach screenshots or a short screen recording of - the new or changed screen on a real device or emulator. Include: - - Light **and** dark themes when the change is visible in both. - - The empty / loading / error states if your change introduces them. - - For Android: device model + Android version (e.g. "Pixel 7, Android 14"). - - For Desktop: OS + window size (e.g. "macOS 14, 1440×900"). +- **Code/logic changes:** paste the test output, CLI command, or log lines + that show the new path executing. CI green is necessary but not + sufficient. +- **UI changes** (`amethyst/`, `desktopApp/`, or `@Composable` code in + `commons/`): attach screenshots or a short recording from a real device or + emulator. See the PR template for the exact checklist (light + dark, + device / OS info, empty / loading / error states). - **Build / Gradle / packaging changes:** paste the `./gradlew` command and - the tail of its output, or the produced artifact name from - `desktopApp/build/compose/binaries/`. -- **Translation-only PRs:** screenshots are not required, but mention which - locale and which strings you touched. + the tail of its output. +- **Translation-only PRs:** mention which locale and which strings you + touched; screenshots not required. -Once you have an established track record in the repository, a short test -plan in the PR description is enough on subsequent PRs. Maintainers may -still ask for screenshots on visual changes regardless of contributor -seniority. +Once you have an established track record, a short test plan is enough on +subsequent PRs. Maintainers may still ask for screenshots on visual changes. -If you cannot run a particular target locally (e.g. you don't have macOS but -your change affects the DMG build), say so explicitly in the PR — don't -claim it works when you haven't checked. Honest "I couldn't test this on -Windows" is far better than a silent guess. +If you can't run a particular target locally (e.g. no macOS, but your change +affects the DMG build), say so explicitly. Honest "I couldn't test this on +Windows" beats a silent guess. ## Reporting bugs and requesting features -We use GitHub issue templates at -[`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/). Please use them rather -than opening a blank issue — they exist so triage can route the report -without a follow-up round trip. +Use the GitHub issue templates at +[`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/) rather than opening a +blank issue — they exist so triage can route the report without a follow-up +round trip. -### Bug report (`[BUG]` title prefix) +**Bug report (`[BUG]` title prefix):** describe the bug, give numbered repro +steps from a fresh app launch, state expected behavior, attach video / +screenshots for anything visual or timing-sensitive, list device info (phone +brand/model, Android version, app version, flavour, Amber version if +applicable), and include a bounty. -Required fields: - -- **Describe the bug** — one-paragraph summary of what's wrong. -- **To Reproduce** — numbered steps (1, 2, 3, …) that walk a maintainer from - a fresh app launch to the failure. "It crashes sometimes" is not a repro; - "tap +, paste this nevent, tap Send" is. -- **Expected behavior** — what should have happened. -- **Video and Screenshots** — attach them whenever the bug is visual, - involves navigation, or is timing-sensitive. -- **Device info** — Phone Brand/Model, Android version, App version, App - flavour (Google Play / F-Droid / Desktop), and Amber version if you sign - with NIP-55. -- **Bounty (in Bitcoin sats)** — see below. - -### Feature request (`[FEATURE]` title prefix) - -Required fields: - -- **Describe the solution you'd like** — what the user-facing outcome is. - Skip implementation suggestions unless they're load-bearing. -- **Bounty (in Bitcoin sats)** — see below. +**Feature request (`[FEATURE]` title prefix):** describe the user-facing +outcome and include a bounty. Skip implementation suggestions unless they're +load-bearing. ### Bounties -Both templates include a bounty field, and the practice is unusually -literal in this project: **maintainer time is allocated by bounty size**. -The templates say so plainly — quoting them: - -> The size of the bounty is proportional to how much this matters to you. If -> no bounty is offered, not even a small one, this bug will not be worked -> on because it doesn't matter to you. - -In practice: - -- Bug fixes and features are prioritized roughly by bounty ÷ effort. -- Even a small bounty (a few hundred sats) outranks a zero-bounty issue. -- Bounties are not refunded if you change your mind, so size them - honestly against how much you want the outcome. -- If you can fix the issue yourself, you don't need a bounty — open the - PR. Bounties exist to move issues that **nobody is currently working on**. -- Security issues do not need a bounty and should not be filed as public - issues — see [SECURITY.md](SECURITY.md). - -### Sending issues over Nostr - -The repository is also mirrored at -[`gitworkshop.dev/repo/amethyst`](https://gitworkshop.dev/repo/amethyst) for -Nostr-native issue tracking, and patches can be sent via -[GitStr](https://github.com/fiatjaf/gitstr) to the address at the bottom of -the README. +Maintainer time is allocated by bounty size. From the templates: *"If no +bounty is offered, not even a small one, this bug will not be worked on +because it doesn't matter to you."* Issues are prioritized by bounty ÷ +effort, even a few hundred sats outrank a zero-bounty issue, and bounties +aren't refunded. If you can fix it yourself, just open the PR — bounties +exist to move issues nobody is currently working on. Security issues do not +need a bounty and should not be filed publicly; see +[SECURITY.md](SECURITY.md). ## Security issues **Do not file security vulnerabilities as public GitHub issues.** Use [GitHub private vulnerability reporting](https://github.com/vitorpamplona/amethyst/security/advisories/new) instead. See [SECURITY.md](SECURITY.md) for scope, expected response times, -and the disclosure policy. +and disclosure policy. ## Development setup @@ -191,45 +140,32 @@ cd amethyst Common Gradle entry points: ```bash -# Run desktop app -./gradlew :desktopApp:run - -# Install Android debug build -./gradlew :amethyst:installDebug -# or specifically: -./gradlew installFdroidDebug -./gradlew installPlayDebug - -# Build Quartz for all targets -./gradlew :quartz:build - -# Full build (compiles all modules + runs tests) -./gradlew build +./gradlew :desktopApp:run # Run desktop app +./gradlew :amethyst:installDebug # Install Android debug build +./gradlew :quartz:build # Build Quartz for all targets +./gradlew build # Full build + tests ``` -## Project layout +## Where code belongs -``` -quartz/ Nostr KMP library — protocol, crypto, models. No UI. -commons/ Shared Compose Multiplatform UI, icons, ViewModels, flows. -quic/ Pure-Kotlin QUIC v1 + HTTP/3 + WebTransport transport library. -nestsClient/ Audio-rooms client (NIP-53) built on :quic and :quartz. -amethyst/ Android app — Activity, layouts, navigation. -desktopApp/ Desktop JVM app — Window, sidebar, keyboard shortcuts. -cli/ `amy` — non-interactive JVM CLI over quartz + commons. -ammolite/ Legacy support module. -``` +Modules: + +- `quartz/` — Nostr KMP library (protocol, crypto, models). **No UI.** +- `commons/` — Shared Compose Multiplatform UI, icons, ViewModels, flows. +- `quic/` — Pure-Kotlin QUIC v1 + HTTP/3 + WebTransport. +- `nestsClient/` — Audio-rooms client (NIP-53) built on `:quic` and + `:quartz`. +- `amethyst/` — Android app: Activity, layouts, navigation. +- `desktopApp/` — Desktop JVM app: Window, sidebar, keyboard shortcuts. +- `cli/` — `amy`, a non-interactive JVM CLI over `quartz` + `commons`. +- `ammolite/` — Legacy support module. Per-module design docs live in `/plans/YYYY-MM-DD-.md`. The -global `docs/plans/` folder is frozen — don't add new files there. - -## Where code belongs (sharing philosophy) +global `docs/plans/` folder is frozen. Before writing a new class or composable, check whether it already exists. -Most logic is already implemented somewhere in the tree — duplicating it is -the #1 cause of PR churn. - -Place new code by purpose: +Most logic is already implemented somewhere — duplicating it is the #1 cause +of PR churn. Place new code by purpose: | What you're adding | Goes in | |---|---| @@ -246,61 +182,41 @@ Hard rules: - `quartz/` has **no UI**. - `cli/` has **no Nostr protocol or business logic** — it's a thin assembly layer over `quartz` + `commons`. If your CLI command needs new behavior, - extract that behavior into `commons/` first. -- ViewModels are platform-agnostic and belong in `commons/commonMain/`. Only - screens (the Composable that wires layout + navigation) stay in the - platform module. -- For platform-specific behavior in a shared file, use `expect`/`actual` — - ask `/kotlin-multiplatform` if unsure. + extract it into `commons/` first. +- ViewModels belong in `commons/commonMain/`. Only screens (the Composable + that wires layout + navigation) stay in the platform module. +- For platform-specific behavior in a shared file, use `expect`/`actual`. ## Workflow 1. **Survey first.** Search the codebase for existing implementations before writing new code. The `.claude/CLAUDE.md` file has a recommended set of - `grep` queries; running them takes a minute and routinely turns up the - exact class you were about to re-invent. + `grep` queries; running them routinely turns up the exact class you were + about to re-invent. 2. **Open a small PR.** One feature or one fix per PR. Refactors that support the change are fine; unrelated refactors are not. 3. **Branch off `main`.** -4. **Run tests + spotless locally** before pushing (see below). -5. **Open the PR** with a description that explains *why*, not just *what*, - and includes the proof-of-testing notes described above. +4. **Run tests + spotless locally** before pushing. +5. **Open the PR** with a description that explains *why*, not just *what*. ## Coding standards -- Kotlin, formatted with Spotless. Always run: - ```bash - ./gradlew spotlessApply - ``` - before considering a task complete. CI runs `./gradlew spotlessCheck` and - will fail on unformatted code. -- Never use `--no-verify` to bypass pre-commit hooks. If a hook fails, - fix the cause. -- Don't add features, refactor, or introduce abstractions beyond what the - task requires. A bug fix doesn't need surrounding cleanup. -- Don't add error handling, fallbacks, or validation for scenarios that - can't happen. Validate at system boundaries (user input, external APIs). -- Prefer editing existing files to creating new ones. Don't create - documentation files (`*.md`) unless asked or unless the change genuinely - warrants one. -- Default to writing no comments. Only add a comment when the *why* is - non-obvious (a workaround, a subtle invariant, behavior that would - surprise a reader). Don't restate what the code does. -- Don't reference the current task, fix, or callers in comments - (e.g. "added for issue #123") — that belongs in the PR description. +- Kotlin, formatted with Spotless. Always run `./gradlew spotlessApply` + before considering a task complete. CI runs `spotlessCheck` and will fail + on unformatted code. +- Never use `--no-verify` to bypass pre-commit hooks. If a hook fails, fix + the cause. +- Default to writing no comments. Add one only when the *why* is non-obvious + (a workaround, a subtle invariant, behavior that would surprise a + reader). Don't restate what the code does, and don't reference the + current task or callers — that belongs in the PR description. ## Tests ```bash -# Unit + KMP common tests across all modules -./gradlew test - -# Android instrumented tests (needs device or emulator) -./gradlew connectedAndroidTest - -# A single module -./gradlew :quartz:test -./gradlew :commons:test +./gradlew test # Unit + KMP common tests, all modules +./gradlew connectedAndroidTest # Android instrumented tests (needs device) +./gradlew :quartz:test # Single module ``` Add tests when: @@ -309,7 +225,7 @@ Add tests when: after. - You add new protocol code in `quartz/` — Nostr event parsing, NIP implementations, and crypto paths should have unit coverage. -- You add anything to `commons/` that has non-trivial state transitions. +- You add anything to `commons/` with non-trivial state transitions. UI changes don't need automated UI tests, but they do need the screenshots described in the proof-of-testing section. @@ -334,26 +250,26 @@ the PR description. Reviewers may ask if you didn't. ### Running them -Each suite has its own README with prerequisites and flags. Quick links: +Each suite has its own README; the non-obvious bits worth flagging up +front: -- **Marmot, DM, audio-rooms (CLI):** [`cli/tests/README.md`](cli/tests/README.md). - Headless variants require only `cargo` + a loopback `nostr-rs-relay`; - the interactive Marmot variant additionally prompts the human to drive - the Amethyst Android UI. -- **MoQ-lite hang + browser:** [`nestsClient/tests/README.md`](nestsClient/tests/README.md). - Opt-in via `-DnestsHangInterop=true` and/or +- **MoQ-lite tiers** are opt-in via `-DnestsHangInterop=true` and `-DnestsBrowserInterop=true` on `:nestsClient:jvmTest`. Cold first run is ~10–13 min per tier; cached runs ~3–7 min. -- **QUIC interop-runner:** [`quic/interop/`](quic/interop/) plus its plan - at `quic/interop/plans/2026-05-06-interop-runner.md`. The standard sweep - is `for peer in aioquic picoquic quic-go quinn; do - quic/interop/run-matrix.sh -s $peer; done`. Always sequential — - `run-matrix.sh` is not safe to invoke concurrently. +- **`quic/interop/run-matrix.sh` is not concurrency-safe.** Run peers + sequentially: `for peer in aioquic picoquic quic-go quinn; do + quic/interop/run-matrix.sh -s $peer; done`. Plan at + `quic/interop/plans/2026-05-06-interop-runner.md`. +- **CLI suites** ([`cli/tests/README.md`](cli/tests/README.md)): headless + variants need only `cargo` + a loopback `nostr-rs-relay`; the interactive + Marmot variant prompts a human to drive the Android UI. If a change is documentation-only, UI-only, build-script-only, or otherwise cannot affect wire bytes / decoded audio / MLS state / DM envelopes, skip the interop suites and say so in the PR description. +## Commits + - Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, `test:`, etc. - One logical change per commit. Squashing during PR review is fine. @@ -368,15 +284,9 @@ the interop suites and say so in the PR description. A good PR description has: 1. **Summary** — 1–3 sentences on what changed and why. -2. **Modules touched** — `quartz`, `commons`, `amethyst`, `desktopApp`, - `cli`, etc. -3. **Test plan** — exactly what you ran, on what platform, with what +2. **Test plan** — exactly what you ran, on what platform, with what result. Include screenshots / recordings for UI changes (mandatory for - non-regular contributors; expected for all contributors on visual - changes). -4. **Risk / rollback** — if the change touches relay subscriptions, the - `NostrClient`, signing, or anything in `Account`/`LocalCache`, call out - what could break and how to revert. + non-regular contributors; expected for everyone on visual changes). We aim to give first-pass feedback within a few days. PRs may sit longer if they're large, touch security-sensitive code, or arrive during a release @@ -386,8 +296,8 @@ window. - Submit translations via [Crowdin](https://crowdin.com/project/amethyst-social), not as direct PRs - to `strings.xml`. Crowdin pushes are integrated through the - `crowdin.yml` workflow. + to `strings.xml`. Crowdin pushes are integrated through the `crowdin.yml` + workflow. - The `/find-missing-translations` skill can help reviewers spot untranslated keys for a target locale.