mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
docs: rewrite AGENTS.md to agents.md / Codex style
Make agent instructions imperative and command-first, add nested crates/vnidrop and shared guides, and keep compose-skill as the UI source of truth with VniDrop-specific overrides.
This commit is contained in:
494
AGENTS.md
494
AGENTS.md
@@ -1,306 +1,308 @@
|
||||
# AGENTS.md — VniDrop agent guide
|
||||
# AGENTS.md
|
||||
|
||||
Instructions for AI agents and humans working in this repository. Read this
|
||||
before making changes. Prefer following existing code over inventing patterns.
|
||||
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`](crates/vnidrop/AGENTS.md) — Rust core
|
||||
- [`shared/AGENTS.md`](shared/AGENTS.md) — Compose Multiplatform UI / KMP
|
||||
|
||||
---
|
||||
|
||||
## 1. Mission and stack
|
||||
## Project overview
|
||||
|
||||
**VniDrop** is a cross-platform local file-transfer app: share files/folders with
|
||||
nearby devices via tickets (copy, QR, NFC), with optional sender approval.
|
||||
VniDrop is a cross-platform **local P2P file transfer** app (Android, iOS, Desktop).
|
||||
|
||||
| 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` |
|
||||
| Layer | Path | Responsibility |
|
||||
|-------|------|----------------|
|
||||
| Rust core | `crates/vnidrop/` | Iroh endpoint, blobs, SQLite, tickets, approval, streaming |
|
||||
| Shared KMP | `shared/` | Compose UI, ViewModels, expect/actual platform bridges |
|
||||
| Hosts | `androidApp/`, `iosApp/`, `desktopApp/` | Thin app shells |
|
||||
|
||||
**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.
|
||||
**Invariant:** UI/platform opens files and handles pickers; **Rust streams bytes**.
|
||||
Do not design features that move transfer payloads through Kotlin heap by default.
|
||||
|
||||
Primary docs (do not re-copy wholesale into PRs):
|
||||
Domain docs (reference, do not paste into PRs):
|
||||
|
||||
- Core send/receive flow: [`crates/vnidrop/CORE_FLOW.md`](crates/vnidrop/CORE_FLOW.md)
|
||||
- Rust test layout: [`crates/vnidrop/tests/README.md`](crates/vnidrop/tests/README.md)
|
||||
- [`crates/vnidrop/CORE_FLOW.md`](crates/vnidrop/CORE_FLOW.md)
|
||||
- [`crates/vnidrop/tests/README.md`](crates/vnidrop/tests/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 2. Hard rules (always)
|
||||
## Absolute rules
|
||||
|
||||
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).
|
||||
1. Prefer PRs into `master`. Do not merge to `master` locally unless the user asks.
|
||||
2. Do not `git push`, force-push, or open a PR unless the user asks.
|
||||
3. If `commit.gpgsign` is enabled, create **signed** commits only. If signing fails
|
||||
(empty `ssh-add -l`), stop and tell the user to unlock the key. Never switch to
|
||||
unsigned commits to “unblock” yourself.
|
||||
4. Change only files required for the task. No drive-by refactors, dependency bumps,
|
||||
or repo-wide formatting.
|
||||
5. Do not force architecture migrations (MVI, Hilt, Nav3, etc.) unless requested.
|
||||
6. Never commit secrets, key material, or passphrases.
|
||||
7. Destructive git (`reset --hard`, `push --force`, dropping DBs) only with explicit
|
||||
user approval.
|
||||
8. **Every bug fix includes a regression test** at the lowest layer that catches it.
|
||||
9. After code changes, run the **relevant** checks in [Build and test](#build-and-test)
|
||||
and fix failures before finishing.
|
||||
|
||||
---
|
||||
|
||||
## 3. Compose Multiplatform — use `compose-skill`
|
||||
## Build and test
|
||||
|
||||
### When to load it
|
||||
Install prerequisites when missing: Rust stable + rustfmt + clippy, JDK 17,
|
||||
Android NDK/SDK only if building Android, Xcode only for iOS.
|
||||
|
||||
For **any** Kotlin UI / presentation work (screens, components, theme,
|
||||
navigation, resources, ViewModel↔UI wiring, accessibility, lists, animation),
|
||||
load and follow:
|
||||
### Rust core (`crates/vnidrop` or workspace root)
|
||||
|
||||
Run from the **repo root** (Cargo workspace):
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace --all-targets
|
||||
```
|
||||
|
||||
Focused:
|
||||
|
||||
```bash
|
||||
cargo test -p vnidrop
|
||||
cargo test -p vnidrop --test output_sink
|
||||
cargo test -p vnidrop --test transfer
|
||||
cargo test -p vnidrop --test approval
|
||||
cargo test -p vnidrop --test lifecycle
|
||||
```
|
||||
|
||||
After finishing Rust edits, format:
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
CI also runs `cargo doc --workspace --no-deps` with `RUSTDOCFLAGS=-D warnings`
|
||||
(see `.github/workflows/rust-core.yml`). Run it before large Rust public-API changes.
|
||||
|
||||
### Shared KMP / Compose (`shared/`)
|
||||
|
||||
```bash
|
||||
./gradlew :shared:jvmTest
|
||||
./gradlew :shared:compileKotlinJvm
|
||||
```
|
||||
|
||||
Other targets (slower / machine-dependent):
|
||||
|
||||
```bash
|
||||
./gradlew :shared:testAndroidHostTest
|
||||
./gradlew :shared:iosSimulatorArm64Test # macOS + Xcode
|
||||
./gradlew :androidApp:assembleDebug
|
||||
./gradlew :desktopApp:run
|
||||
```
|
||||
|
||||
**Note:** `jvmTest` CI runs on **macOS** because Gobley host cargo is enabled for
|
||||
the current Gobley host; Linux JVM cargo may be disabled in
|
||||
`shared/build.gradle.kts`. Prefer macOS for local parity with CI.
|
||||
|
||||
### What to run before finishing
|
||||
|
||||
| You changed… | Minimum verification |
|
||||
|--------------|----------------------|
|
||||
| `crates/vnidrop/**` only | `cargo fmt`, `cargo clippy … -D warnings`, `cargo test -p vnidrop` |
|
||||
| Cancel / export / sinks | Above + `cargo test -p vnidrop --test output_sink` |
|
||||
| `shared/**` only | `./gradlew :shared:jvmTest` |
|
||||
| Both | Rust suite + `:shared:jvmTest` |
|
||||
| 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|iosMain|jvmMain/ # expect/actual implementations
|
||||
```
|
||||
|
||||
### Platform file rules (do not violate)
|
||||
|
||||
- Desktop / path-based iOS: 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::MutexGuard` or other guards across `.await`.
|
||||
- Prefer `Handle::block_on` via existing `VnidropCore::block_on` for 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:
|
||||
|
||||
```text
|
||||
.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.
|
||||
- Open at most one `references/*.md` file 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 MVI `onEvent` base — adapt, do not rewrite.
|
||||
- Theme via `LocalVniDropColors` / `VniDropThemeTokens` only.
|
||||
Brand primary (light): HSL `271, 91%, 65%` ≈ `#A855F7`.
|
||||
- Strings: CMP `Res.string.*` / composeResources — not Android `R` in `commonMain`.
|
||||
- Verify multiplatform target support before adding AndroidX/Jetpack deps to
|
||||
`commonMain`.
|
||||
|
||||
### How to use it
|
||||
|
||||
1. Read existing feature code first (conventions beat generic tutorials).
|
||||
2. Apply the skill’s 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 skill’s 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-skill’s “Existing Project Policy” applies: preserve working structure.
|
||||
Details: [`shared/AGENTS.md`](shared/AGENTS.md).
|
||||
|
||||
---
|
||||
|
||||
## 4. Code comments
|
||||
## Testing instructions
|
||||
|
||||
Comments exist for **future readers who already know the language**.
|
||||
- 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.
|
||||
|
||||
### 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
|
||||
Layout:
|
||||
|
||||
| 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`](crates/vnidrop/tests/README.md) for Rust
|
||||
organization rules.
|
||||
|
||||
### Commands agents should run (as relevant)
|
||||
|
||||
```bash
|
||||
# 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.yml` — `jvmTest` (and related shared paths)
|
||||
|
||||
If you change Rust cancel/export concurrency, at least run:
|
||||
|
||||
```bash
|
||||
cargo test -p vnidrop --test output_sink
|
||||
```
|
||||
|-------|----------|
|
||||
| Rust unit / private | `crates/vnidrop/src/tests/` |
|
||||
| Rust integration | `crates/vnidrop/tests/` |
|
||||
| Shared logic | `shared/src/commonTest/` |
|
||||
| Shared Compose/JVM | `shared/src/jvmTest/` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Git and PR conventions
|
||||
## Git and PR instructions
|
||||
|
||||
### Branches
|
||||
|
||||
Name after **what changes**, not roadmap steps:
|
||||
Name the change, not a roadmap step:
|
||||
|
||||
- Good: `feat/folder-share`, `fix/cancel-export-hang`, `refactor/split-runtime-module`, `docs/agents-md`
|
||||
- Bad: `feat/step3-remaining`, `temp`, `wip`
|
||||
- Good: `feat/folder-share`, `fix/cancel-export-hang`, `docs/agents-md`
|
||||
- Bad: `feat/step3-remaining`, `wip`, `temp`
|
||||
|
||||
Delete merged feature branches **locally and on origin** after the PR lands;
|
||||
start the next task from updated `master`.
|
||||
After a PR merges: delete the feature branch **locally and on `origin`**, then
|
||||
branch 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.
|
||||
- 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** and **why**.
|
||||
- **Test plan must be useful for this PR**:
|
||||
- Exact commands to run, and/or
|
||||
- 1–2 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.
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation checklist (before finishing a task)
|
||||
## Security considerations
|
||||
|
||||
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.
|
||||
- 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).
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
## Common tasks → start files
|
||||
|
||||
| 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` |
|
||||
| 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` |
|
||||
|
||||
---
|
||||
|
||||
*Last aligned with post–PR #10 tree (`runtime/` split, multi-file/folder share,
|
||||
iOS QR/NFC, per-receiver progress, cancel-export fixes).*
|
||||
## Anti-patterns (never)
|
||||
|
||||
- Streaming multi-MB transfer data through Kotlin as the primary design
|
||||
- Passing Android **directory** FDs into Rust
|
||||
- Nested exclusive `Runtime::block_on` that 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
|
||||
|
||||
1. Read this file + nearest nested `AGENTS.md`.
|
||||
2. For Compose/UI: load `compose-skill`.
|
||||
3. Smallest correct change; tests for bugs/behavior changes.
|
||||
4. Run relevant build/test commands; fix failures.
|
||||
5. Sparse comments only where non-obvious.
|
||||
6. Commit (signed) / push / PR only as the user requests.
|
||||
7. Summarize what changed and what you ran.
|
||||
|
||||
128
crates/vnidrop/AGENTS.md
Normal file
128
crates/vnidrop/AGENTS.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# AGENTS.md — `crates/vnidrop` (Rust core)
|
||||
|
||||
Nearest guide when editing under `crates/vnidrop/`. Root [`AGENTS.md`](../../AGENTS.md)
|
||||
still applies; this file wins for Rust-specific commands and conventions.
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
This crate is the transfer backend exposed to Kotlin via UniFFI (`VnidropCore`).
|
||||
It owns Iroh, blobs, SQLite history, tickets, approval handshake, and file streaming.
|
||||
|
||||
Read [`CORE_FLOW.md`](CORE_FLOW.md) before changing send/receive/export/cancel.
|
||||
|
||||
---
|
||||
|
||||
## Commands (run from repo root)
|
||||
|
||||
Always prefer workspace commands so lockfile/fmt stay consistent:
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test -p vnidrop
|
||||
cargo test --workspace --all-targets
|
||||
```
|
||||
|
||||
Focused integration suites:
|
||||
|
||||
```bash
|
||||
cargo test -p vnidrop --test transfer
|
||||
cargo test -p vnidrop --test approval
|
||||
cargo test -p vnidrop --test lifecycle
|
||||
cargo test -p vnidrop --test output_sink
|
||||
```
|
||||
|
||||
Docs (CI uses `-D warnings`):
|
||||
|
||||
```bash
|
||||
RUSTDOCFLAGS='-D warnings' cargo doc -p vnidrop --no-deps
|
||||
```
|
||||
|
||||
Run `cargo fmt --all` after finishing Rust edits without asking.
|
||||
|
||||
---
|
||||
|
||||
## Module layout
|
||||
|
||||
```
|
||||
src/
|
||||
runtime/
|
||||
mod.rs # CoreInner, startup recovery, emit helpers
|
||||
facade.rs # UniFFI surface, block_on, cancel entry
|
||||
share.rs # share / import
|
||||
receive.rs # receive, download, export, OutputSinkFile
|
||||
lifecycle.rs # cancel share, delete, status, access mode, shutdown
|
||||
provider.rs # provider messages, per-peer transfer progress
|
||||
filesystem.rs # collect sources, atomic publish, path rules
|
||||
repository.rs # SQLite
|
||||
approval.rs / handshake.rs / ticket.rs / access_policy.rs / event_hub.rs
|
||||
api.rs # UniFFI records/enums
|
||||
tests/ # crate-private unit tests
|
||||
tests/ # public-API integration tests + support/
|
||||
```
|
||||
|
||||
**Do not** reassemble a single huge `runtime.rs`. Prefer new focused modules if a
|
||||
file approaches ~800 LoC of non-test code.
|
||||
|
||||
---
|
||||
|
||||
## Hard constraints
|
||||
|
||||
1. **Public API stability:** UniFFI surface changes break Kotlin. Prefer additive
|
||||
changes; update shared Kotlin call sites in the same change when required.
|
||||
2. **Streaming stays in Rust.** Platform passes paths or FDs; core does not pull
|
||||
whole files into Kotlin.
|
||||
3. **Android FDs are files only.** `SourceKind::FileDescriptor` with
|
||||
`is_directory=true` must fail; directories are expanded on the platform side.
|
||||
4. **Cancel:** signal active-transfer oneshot **synchronously** before async DB
|
||||
work. Use existing `take_active_transfer` / facade cancel path. Do not reintroduce
|
||||
nested exclusive `Runtime::block_on` deadlocks.
|
||||
5. **No lock across await:** Clippy `await_holding_lock` fails CI.
|
||||
6. **ReceiveOutputSink:** after successful `start_file`, exactly one of
|
||||
`finish_file` or `abort_file` (see `OutputSinkFile` Drop).
|
||||
7. **No-overwrite publish** for path receives (temp + hard link / exclusive rename).
|
||||
8. Integration tests must use the **public** API + `tests/support/` only.
|
||||
|
||||
---
|
||||
|
||||
## Code style (Rust)
|
||||
|
||||
- Clippy clean with `-D warnings`.
|
||||
- Prefer exhaustive `match`; avoid catch-all arms that hide new enum variants.
|
||||
- Prefer comparing whole objects in tests when practical.
|
||||
- Comment only non-obvious why (concurrency, durability, platform FS quirks).
|
||||
- Do not add one-off private helpers used once if inline is clearer.
|
||||
- Prefer private modules; export deliberately via `lib.rs` / UniFFI.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit / private: `src/tests/` (see crate `tests.rs` paths).
|
||||
- Integration: `tests/*.rs` + `tests/support/mod.rs` (`TestNode`, `MemoryOutputSink`,
|
||||
`CoreGuard`, etc.).
|
||||
- Bug fixes need a regression test.
|
||||
- Prefer gates/latches over multi-second sleeps (see output_sink cancel test).
|
||||
- Failure tests: durable status and/or events when applicable.
|
||||
- Recovery tests: shut down core, reopen same data dir.
|
||||
|
||||
Details: [`tests/README.md`](tests/README.md).
|
||||
|
||||
---
|
||||
|
||||
## PR / verify checklist for this crate
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test -p vnidrop
|
||||
```
|
||||
|
||||
If you touched cancel, export, or sinks, also:
|
||||
|
||||
```bash
|
||||
cargo test -p vnidrop --test output_sink
|
||||
```
|
||||
126
shared/AGENTS.md
Normal file
126
shared/AGENTS.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# AGENTS.md — `shared/` (Compose Multiplatform + KMP)
|
||||
|
||||
Nearest guide when editing under `shared/`. Root [`AGENTS.md`](../AGENTS.md)
|
||||
still applies; this file wins for UI/KMP work.
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
`shared` is the multiplatform app layer: Compose UI, feature ViewModels, and
|
||||
`expect`/`actual` bridges into Android, iOS, and desktop. Native transfer work
|
||||
goes through UniFFI `VnidropCore` (see `crates/vnidrop`).
|
||||
|
||||
---
|
||||
|
||||
## Compose skill (required for UI work)
|
||||
|
||||
For screens, components, theme, navigation, resources, ViewModel↔UI wiring,
|
||||
lists, animation, accessibility:
|
||||
|
||||
1. Load [`.codex/skills/compose-skill/SKILL.md`](../.codex/skills/compose-skill/SKILL.md).
|
||||
2. Follow its workflow and defaults.
|
||||
3. Open **at most one** file under `.codex/skills/compose-skill/references/` when
|
||||
the skill’s Quick Routing table says you need deeper guidance.
|
||||
4. Do **not** invent a parallel Compose style guide.
|
||||
|
||||
### Project policy (overrides generic skill defaults)
|
||||
|
||||
| Topic | Do this |
|
||||
|-------|---------|
|
||||
| Architecture | Keep **MVVM-style** ViewModels: immutable `*State`, `StateFlow`, **named methods**. Do not force MVI `onEvent` sealed hierarchies unless asked. |
|
||||
| Structure | Feature packages under `com.vnidrop.app.feature.*`; thin route/wiring + screen/composables. |
|
||||
| Theme | Only `LocalVniDropColors` / `VniDropThemeTokens` (`ui/theme/VniDropTheme.kt`). Brand primary light ≈ `#A855F7` (HSL 271, 91%, 65%). |
|
||||
| Strings | CMP composeResources / `Res.string.*` — not Android `R` in `commonMain`. |
|
||||
| DI | Follow existing `AppGraph` construction; no unprompted Hilt/Koin migration. |
|
||||
| Platform | `androidMain` / `iosMain` / `jvmMain` for pickers, SAF, security-scoped URLs, NFC/QR. |
|
||||
| Dependencies | Before adding Jetpack/AndroidX to `commonMain`, verify multiplatform artifacts for all targets. |
|
||||
|
||||
compose-skill “Existing Project Policy”: adapt to this repo; do not force-migrate.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
./gradlew :shared:jvmTest
|
||||
./gradlew :shared:compileKotlinJvm
|
||||
```
|
||||
|
||||
Optional:
|
||||
|
||||
```bash
|
||||
./gradlew :shared:testAndroidHostTest
|
||||
./gradlew :shared:iosSimulatorArm64Test
|
||||
./gradlew :desktopApp:run
|
||||
./gradlew :androidApp:assembleDebug
|
||||
```
|
||||
|
||||
CI `:shared:jvmTest` runs on **macOS** (Gobley host cargo). Prefer macOS for
|
||||
local parity.
|
||||
|
||||
When Kotlin changes touch UniFFI-generated APIs, rebuild/test with a full
|
||||
`jvmTest` so Gobley/native pieces stay aligned.
|
||||
|
||||
---
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
src/
|
||||
commonMain/kotlin/com/vnidrop/app/
|
||||
core/ # models, CoreGateway, FilePicker interfaces
|
||||
feature/send|receive|approvals|settings|app/
|
||||
ui/ # theme, components, navigation, feedback, state helpers
|
||||
commonMain/composeResources/
|
||||
androidMain|iosMain|jvmMain/
|
||||
commonTest|jvmTest|...
|
||||
```
|
||||
|
||||
### Platform file bridging (must preserve)
|
||||
|
||||
- **Android share:** open content URIs as FDs; expand **folder trees** to per-file
|
||||
documents with relative `displayName` paths before calling Rust
|
||||
(`FileSystemService.android.kt` / `expandShareDirectory`).
|
||||
- **Android receive:** MediaStore Downloads sink and/or SAF tree write sink.
|
||||
- **iOS:** keep security-scoped leases alive while Rust reads paths.
|
||||
- **Desktop:** filesystem paths; directories may be marked `isDirectory` for Rust walk.
|
||||
|
||||
Never pass a directory as a single Android FD into `SourceKind.FILE_DESCRIPTOR`.
|
||||
|
||||
---
|
||||
|
||||
## Code style (Kotlin)
|
||||
|
||||
- Match existing feature style (imports, naming, state updates via `update { }`).
|
||||
- Composables render state and invoke callbacks; no business rules in `@Composable`
|
||||
bodies (network, share creation, ticket parse — ViewModel/core).
|
||||
- Prefer stable list keys from domain IDs.
|
||||
- Comments only for non-obvious platform or concurrency reasons.
|
||||
- Do not hard-code brand colors; use theme tokens.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
- Logic: `src/commonTest/` (e.g. ViewModel fakes, `AppUiModels` progress helpers).
|
||||
- Compose/JVM: `src/jvmTest/`.
|
||||
- Add/adjust tests when changing state machines, progress aggregation, or
|
||||
share/receive eligibility.
|
||||
- Prefer fakes in `commonTest` support over real UniFFI in pure unit tests.
|
||||
|
||||
```bash
|
||||
./gradlew :shared:jvmTest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Streaming transfer bytes through Kotlin as the main design
|
||||
- Directory FDs on Android
|
||||
- New DI framework “because best practice”
|
||||
- Loading every compose-skill reference file for a small UI tweak
|
||||
- Android `R.string` in `commonMain`
|
||||
Reference in New Issue
Block a user