Files
vnidrop/AGENTS.md
Hammed Abass 4de004cc1f docs: add root AGENTS.md for future AI collaborators
Capture stack map, hard rules, compose-skill usage, sparse comment
policy, testing/CI commands, git/PR conventions, and anti-patterns so
agents adapt to VniDrop without rediscovering them.
2026-07-12 20:21:44 +02:00

12 KiB
Raw Blame History

AGENTS.md — VniDrop agent guide

Instructions for AI agents and humans working in this repository. Read this before making changes. Prefer following existing code over inventing patterns.


1. Mission and stack

VniDrop is a cross-platform local file-transfer app: share files/folders with nearby devices via tickets (copy, QR, NFC), with optional sender approval.

Layer Location Role
Rust core crates/vnidrop/ Iroh networking, blob store, SQLite, approval, tickets, streaming
Shared UI shared/ Compose Multiplatform UI, ViewModels, platform bridges
Apps androidApp/, iosApp/, desktopApp/ Thin hosts
UniFFI Gobley + crates/vnidrop/uniffi.toml Kotlin bindings to VnidropCore

Non-negotiable product rule: platform/UI code opens files and reacts to events; Rust owns byte streaming. Do not pull transfer payloads through Kotlin heap as the default path.

Primary docs (do not re-copy wholesale into PRs):


2. Hard rules (always)

  1. Default to PRs into master. Do not merge locally to master unless the user explicitly asks.
  2. Do not push, force-push, or open a PR unless the user asks.
  3. Signed commits when the repo enables signing (commit.gpgsign). If signing fails (empty agent / passphrase), stop and ask the user to unlock the key (ssh-add …). Do not silently create unsigned commits.
  4. Minimal scope. Change only what the task requires. No drive-by refactors, dependency bumps, or reformatting unrelated files.
  5. Do not force architecture migrations. Adapt to existing patterns (ViewModels, feature packages, Rust modules). Structural rewrites only when requested or when fixing a clear violation.
  6. Never commit secrets, API keys, keychains, or passphrases. Never paste user passphrases into chat or files.
  7. Destructive git (reset --hard, push --force, dropping data) only with explicit user approval.
  8. Bug fixes need tests at the correct layer (see §6).

3. Compose Multiplatform — use compose-skill

When to load it

For any Kotlin UI / presentation work (screens, components, theme, navigation, resources, ViewModel↔UI wiring, accessibility, lists, animation), load and follow:

.codex/skills/compose-skill/SKILL.md

Do not invent a parallel Compose style guide. The skill is the source of truth for Compose/CMP defaults.

How to use it

  1. Read existing feature code first (conventions beat generic tutorials).
  2. Apply the skills core rules (state in ViewModel, dumb UI, unidirectional data).
  3. For advanced topics only, open one file under .codex/skills/compose-skill/references/ using the skills Quick Routing table (e.g. testing.md, performance.md, cross-platform.md, resources.md).
  4. Do not load the entire references/ tree “just in case.”

VniDrop-specific overrides (compose-skill adapts; do not “fix” these)

Topic VniDrop convention
Architecture MVVM-style feature ViewModels: immutable *State data class, StateFlow, named public methods (not a mandatory MVI onEvent sealed hierarchy). One-shot UI feedback often via shared snackbar/UiMessage rather than a formal Effect channel—match the feature you edit.
Packages com.vnidrop.app.feature.<send|receive|settings|approvals|app> + ui/* + core/*
Route / Screen split Prefer thin *Route (wiring) + *Screen / feature composables (render + callbacks).
Theme Use LocalVniDropColors / VniDropThemeTokens in shared/.../ui/theme/VniDropTheme.kt. Do not hard-code one-off brand colors. Brand primary (light): HSL 271, 91%, 65%#A855F7.
Strings / drawables Compose Multiplatform resources (composeResources, Res.string.*), not Android R in commonMain.
DI Follow existing AppGraph / construction patterns; do not introduce Hilt/Koin migrations unprompted.
Platform code expect/actual or interfaces under androidMain / iosMain / jvmMain. Android SAF/tree/FDs and iOS security-scoped URLs stay on the platform side.

Compose-skills “Existing Project Policy” applies: preserve working structure.


4. Code comments

Comments exist for future readers who already know the language.

Do comment

  • Non-obvious why (concurrency, cancel ordering, durability, security).
  • Platform traps (SAF cannot pass directory FDs; security-scoped lease lifetime; MediaStore Downloads vs path probes).
  • Invariants and failure modes (“exactly one of finish/abort after start_file”).
  • Public or crate-boundary contracts that tests rely on.

Do not comment

  • Restating the next line of code (// increment i, // return result).
  • Section banners that only repeat the function name.
  • Tutorial-style narration of self-explanatory control flow.
  • Large blocks duplicated from CORE_FLOW.md or this file.

Prefer a short module///! doc or one high-signal line over many low-signal lines. Match the density of existing Rust comments in crates/vnidrop/src/runtime/ and Kotlin comments on Android expand/share paths.


5. Architecture map (where to edit)

Rust core (crates/vnidrop/src/)

Area Path
UniFFI + runtime entry runtime/facade.rs (VnidropCore)
Share / import runtime/share.rs
Receive / export / sinks runtime/receive.rs
Cancel / delete / status / access runtime/lifecycle.rs
Provider events, send progress per peer runtime/provider.rs
Startup, CoreInner, emit helpers runtime/mod.rs
SQLite repository.rs
Paths, import collect, atomic publish filesystem.rs
Approval handshake approval.rs, handshake.rs
Tickets ticket.rs
Access policy access_policy.rs
Events event_hub.rs, api.rs (CoreEvent)

Runtime was split intentionally: keep modules focused; do not reassemble a monolithic runtime.rs.

Shared KMP (shared/src/)

Area Path
Core gateway / models commonMain/.../core/
Send UI commonMain/.../feature/send/
Receive UI commonMain/.../feature/receive/
Approvals commonMain/.../feature/approvals/
Settings commonMain/.../feature/settings/
Theme / shell / components commonMain/.../ui/
Android pickers, SAF, MediaStore, NFC/QR androidMain/
iOS pickers, security scope, NFC/QR iosMain/
Desktop paths / pickers jvmMain/

Platform file rules (summary)

  • Desktop / path-based iOS: SourceKind::Path or security-scoped URL→path; directories walked in Rust when is_directory.
  • Android share: open documents as FDs; never a directory FD. Folder share expands SAF trees in Kotlin (expandShareDirectory) into per-file FDs with relative displayName paths.
  • Android receive default: system Downloads via MediaStore sink when available; custom trees via SAF write sink.
  • Receive publish: no-overwrite temps + hard link / exclusive rename (see CORE_FLOW.md).

Progress / multi-receiver

  • Send-side byte progress is attributed with endpoint_id (and connection_id) on provider transfer events; UI aggregates via progressForReceiver / activeSendProgress in shared/.../ui/state/AppUiModels.kt.
  • Delivery completion is a separate delivery phase / receiver request status.

6. Testing

Policy

  • Every bug fix includes a regression test at the lowest layer that catches it.
  • Prefer deterministic setups (latches/gates, fixed sizes) over multi-second sleeps. If polling is required: short interval + bounded timeout + useful assertion message.
  • Integration tests use the public UniFFI API (tests/support/), not private internals exposed only for tests.
  • Failure tests should check durable status and/or events when relevant, not only the error string.

Where tests live

Layer Location
Rust unit / crate-private crates/vnidrop/src/tests/
Rust integration crates/vnidrop/tests/ + tests/support/
Shared pure logic shared/src/commonTest/
Shared Compose / JVM shared/src/jvmTest/

See crates/vnidrop/tests/README.md for Rust organization rules.

Commands agents should run (as relevant)

# Rust format / lint / tests
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test -p vnidrop

# Shared KMP (desktop JVM suite)
./gradlew :shared:jvmTest

CI (path-filtered):

  • .github/workflows/rust-core.yml — fmt, clippy -D warnings, tests
  • .github/workflows/shared-kmp.ymljvmTest (and related shared paths)

If you change Rust cancel/export concurrency, at least run:

cargo test -p vnidrop --test output_sink

7. Git and PR conventions

Branches

Name after what changes, not roadmap steps:

  • Good: feat/folder-share, fix/cancel-export-hang, refactor/split-runtime-module, docs/agents-md
  • Bad: feat/step3-remaining, temp, wip

Delete merged feature branches locally and on origin after the PR lands; start the next task from updated master.

Commits

  • Prefer conventional style used in history: feat(scope):, fix(scope):, refactor(scope):, docs:, style:, ci:.
  • Subject focuses on why / user-visible outcome, complete sentences in the body when needed.
  • Keep commits reviewable; do not mix unrelated features.

Pull requests

  • Title matches the main change.
  • Summary: short bullets of what and why.
  • Test plan must be useful for this PR:
    • Exact commands to run, and/or
    • 12 concrete device/UI scenarios that would catch a regression.
  • Avoid generic filler (“CI green”, “test everything”) with no commands or scenarios.

Workflow preferences established in this project

  • User often requires say-so before push/PR.
  • Prefer cleaning up remote feature branches after merge.
  • When SSH signing fails, unblock with agent unlock—not policy bypass.

8. Implementation checklist (before finishing a task)

  1. Read surrounding code and this file; for UI, load compose-skill.
  2. Implement the smallest correct change.
  3. Add/adjust tests for behavior changes and bug fixes.
  4. Run the relevant format/lint/test commands.
  5. Comments only where they add non-obvious information.
  6. Commit (signed) if asked; push/PR only if asked.
  7. Leave a concise summary of what changed and how it was verified.

9. Anti-patterns (do not)

  • Streaming multi-MB transfer data through Kotlin as the primary design.
  • Passing Android directory FDs into Rust.
  • Nested exclusive Runtime::block_on patterns that deadlock cancel during receive (use the existing handle-based entry / sync cancel signal approach).
  • Holding locks across .await (Clippy await_holding_lock fails CI).
  • Rebuilding a single 1.7k-line runtime.rs.
  • Migrating the whole app to MVI/Hilt/Nav3 “because best practice.”
  • Adding dependencies without verifying multiplatform target support.
  • Flaky tests that sleep for tens of seconds hoping races resolve.
  • Unsigned commits when signing is required, or force-push without consent.

10. Quick file index for common tasks

Task Start here
Share import / multi-file / folders runtime/share.rs, filesystem.rs, platform FileSystemService.*
Receive path / sink export runtime/receive.rs, Android/iOS sinks
Cancel / delete / stop share runtime/lifecycle.rs, facade.rs cancel path
Per-receiver send progress runtime/provider.rs, AppUiModels.kt, TransferDetails.kt
Approvals UI feature/approvals/, approval.rs
Ticket / QR / NFC TransferShareActions.*, ReceiveInvitationActions.*
Theme / brand color ui/theme/VniDropTheme.kt
Compose skill .codex/skills/compose-skill/SKILL.md

Last aligned with postPR #10 tree (runtime/ split, multi-file/folder share, iOS QR/NFC, per-receiver progress, cancel-export fixes).