10 KiB
AGENTS.md
Operational instructions for coding agents working in this repository.
Humans: see README.md for product overview and run configs.
Agents: read this file (and the nearest nested AGENTS.md) before editing.
Nested guides take precedence when editing under those trees:
crates/vnidrop/AGENTS.md— Rust coreshared/AGENTS.md— Compose Multiplatform UI / KMP
Project overview
VniDrop is a cross-platform local P2P file transfer app.
| Layer | Path | Responsibility |
|---|---|---|
| Rust core | crates/vnidrop/ |
Iroh endpoint, blobs, SQLite, tickets, approval, streaming |
| Shared KMP | shared/ |
Compose UI and platform bridges for Android, Windows, and Linux |
| Compose hosts | androidApp/, desktopApp/ |
Thin Android and Windows/Linux app shells |
| Apple app | apple/ |
Native SwiftUI UI using generated Rust/UniFFI Swift bindings |
Invariant: UI/platform opens files and handles pickers; Rust streams bytes. Do not design features that move transfer payloads through Kotlin heap by default.
Domain docs (reference, do not paste into PRs):
Absolute rules
- Prefer PRs into
master. Do not merge tomasterlocally unless the user asks. - Do not
git push, force-push, or open a PR unless the user asks. - If
commit.gpgsignis enabled, create signed commits only. If signing fails (emptyssh-add -l), stop and tell the user to unlock the key. Never switch to unsigned commits to “unblock” yourself. - Change only files required for the task. No drive-by refactors, dependency bumps, or repo-wide formatting.
- Do not force architecture migrations (MVI, Hilt, Nav3, etc.) unless requested.
- Never commit secrets, key material, or passphrases.
- Destructive git (
reset --hard,push --force, dropping DBs) only with explicit user approval. - Every bug fix includes a regression test at the lowest layer that catches it.
- After code changes, run the relevant checks in Build and test and fix failures before finishing.
Build and test
Install prerequisites when missing: GNU Make + Bash, Rust stable + rustfmt + clippy, JDK 17, Android NDK/SDK only if building Android, Xcode only for the native Apple app.
Rust core (crates/vnidrop or workspace root)
Run from the repo root (Cargo workspace):
make check-rust
Focused:
make test-rust
make test-rust-output-sink
make test-rust-transfer
make test-rust-approval
make test-rust-lifecycle
After finishing Rust edits, format:
make format
make check-rust includes documentation with warnings denied, matching
.github/workflows/rust-core.yml.
Shared KMP / Compose (shared/)
make check-shared
Other targets (slower / machine-dependent):
make test-android-host
make check-android
make run-desktop
Note: jvmTest CI runs on Linux. Gobley host cargo is enabled for the
current host and architecture, so local desktop builds embed their matching
Rust library.
What to run before finishing
| You changed… | Minimum verification |
|---|---|
crates/vnidrop/** only |
make check-rust |
| Cancel / export / sinks | Above + make test-rust-output-sink |
shared/** only |
make test-shared |
| Both | make test-rust test-shared |
| Docs only | No suite required; verify links/paths |
Do not kill long cargo / Gradle runs mid-flight unless they hang past several
minutes with no output; first builds are slow.
Repository map (edit here)
Rust runtime (keep split; do not re-merge into one file)
crates/vnidrop/src/runtime/
mod.rs # CoreInner, startup recovery, emit helpers
facade.rs # UniFFI VnidropCore + block_on
share.rs # import / share
receive.rs # receive, download, export, output sinks
lifecycle.rs # cancel, delete, status, access mode, shutdown
provider.rs # provider events, per-connection send progress
Other core modules: filesystem.rs, repository.rs, approval.rs,
handshake.rs, ticket.rs, access_policy.rs, event_hub.rs, api.rs.
Shared app
shared/src/commonMain/kotlin/com/vnidrop/app/
core/ # CoreGateway, models, pickers interfaces
feature/send|receive|approvals|settings|app/
ui/theme|components|navigation|feedback|state/
androidMain|jvmMain/ # expect/actual implementations
Platform file rules (do not violate)
- Windows/Linux desktop: paths; directory walk in Rust when
is_directory. - Android share: ParcelFileDescriptor file FDs only — never a directory FD. Folder share expands SAF trees in Kotlin to per-file FDs + relative names.
- Android receive default: MediaStore Downloads sink; custom trees via SAF write.
- Receive publish: no-overwrite temp + hard link / exclusive rename
(see
CORE_FLOW.md).
Code style
General
- Match surrounding code (naming, imports, error handling).
- Prefer small, reviewable diffs. Avoid files growing past ~800 LoC without splitting when adding substantial logic.
- Do not add one-off helpers used only once if an inline block is clearer.
- Prefer exhaustive
when/match; avoid wildcards that hide new cases.
Comments (strict)
Comment why, invariants, and platform/concurrency traps only.
- Do comment: cancel-before-await ordering, SAF/FD limits, security-scoped leases, “exactly one finish/abort after start_file”, durability rules.
- Do not comment: restating the next line, tutorial narration, section banners that repeat the function name, pasted docs from this file.
Rust
- Follow Clippy with
-D warnings(CI fails otherwise). - Do not hold
std::sync::MutexGuardor other guards across.await. - Prefer
Handle::block_onvia existingVnidropCore::block_onfor concurrent API entry; cancel signals active transfers synchronously before async work. - Prefer private modules; export only what UniFFI / other crates need.
- New public traits/types: short docs when the role is non-obvious.
Kotlin / Compose
For UI and presentation work, load and follow the in-repo skill:
.codex/skills/compose-skill/SKILL.md
- Open at most one
references/*.mdfile when the skill’s Quick Routing requires it. - Do not invent a second Compose style guide.
- VniDrop uses MVVM-style ViewModels (
*State+StateFlow+ named methods), not a forced MVIonEventbase — adapt, do not rewrite. - Theme via
LocalVniDropColors/VniDropThemeTokensonly. Brand primary (light): HSL271, 91%, 65%≈#A855F7. - Strings: CMP
Res.string.*/ composeResources — not AndroidRincommonMain. - Verify multiplatform target support before adding AndroidX/Jetpack deps to
commonMain.
Details: shared/AGENTS.md.
Testing instructions
- Prefer deterministic tests (gates, fixed sizes, public API fixtures).
- Avoid long sleeps; if polling is required: short interval + hard timeout + clear assertion message.
- Rust integration tests use public UniFFI API +
tests/support/only. - Failure paths: assert durable status and/or events when applicable, not only the error string.
- Do not add tests for pure static constants.
- Do not add negative tests for code you deleted.
- Prefer comparing whole objects when equality is meaningful.
Layout:
| Layer | Location |
|---|---|
| Rust unit / private | crates/vnidrop/src/tests/ |
| Rust integration | crates/vnidrop/tests/ |
| Shared logic | shared/src/commonTest/ |
| Shared Compose/JVM | shared/src/jvmTest/ |
Git and PR instructions
Branches
Name the change, not a roadmap step:
- Good:
feat/folder-share,fix/cancel-export-hang,docs/agents-md - Bad:
feat/step3-remaining,wip,temp
After a PR merges: delete the feature branch locally and on origin, then
branch from updated master.
Commits
- Style in history:
feat(scope):,fix(scope):,refactor(scope):,docs:,ci:. - Subject = outcome; body only when needed.
- Signed when repo requires it.
Pull requests
- Title matches the main change.
- Summary: short bullets of what/why.
- Test plan must be executable for this PR:
- exact commands, and/or
- 1–2 concrete scenarios that would catch a regression.
- No filler plans (“everything works”, “CI green”) without commands or scenarios.
Security considerations
- Treat tickets and endpoint IDs as sensitive enough not to log full blobs in production paths.
- Do not weaken approval/access checks for convenience.
- Do not store secrets in the repo; app data dirs and key files stay out of git.
- Be careful with file publish races (no-clobber rename/link policy exists for a reason).
Common tasks → start files
| Task | Start here |
|---|---|
| Share / multi-file / folders | runtime/share.rs, filesystem.rs, platform FileSystemService.* |
| Receive / export / sinks | runtime/receive.rs |
| Cancel / delete / stop share | runtime/lifecycle.rs, facade.rs |
| Per-receiver send progress | runtime/provider.rs, ui/state/AppUiModels.kt |
| Approvals | feature/approvals/, approval.rs |
| QR / NFC invitations | TransferShareActions.*, ReceiveInvitationActions.* |
| Theme / brand | ui/theme/VniDropTheme.kt |
| Compose skill | .codex/skills/compose-skill/SKILL.md |
Anti-patterns (never)
- Streaming multi-MB transfer data through Kotlin as the primary design
- Passing Android directory FDs into Rust
- Nested exclusive
Runtime::block_onthat deadlocks cancel during receive - Holding locks across
.await - Rebuilding a monolithic
runtime.rs - Flaky multi-minute sleeps in tests
- Unsigned commits when signing is required
- Force-push or secret commits without explicit user direction
Implementation checklist
- Read this file + nearest nested
AGENTS.md. - For Compose/UI: load
compose-skill. - Smallest correct change; tests for bugs/behavior changes.
- Run relevant build/test commands; fix failures.
- Sparse comments only where non-obvious.
- Commit (signed) / push / PR only as the user requests.
- Summarize what changed and what you ran.