Focuser is an open-source, cross-platform website and application blocker (similar to Cold Turkey Blocker). Built in Rust for maximum performance and safety. Targets Windows, macOS, and Linux.
- Workspace layout:
crates/contains all Rust cratesfocuser-common— Shared types, error types, constants, platform abstractionsfocuser-core— Rules engine, database, block evaluation, scheduling logicfocuser-service— System daemon/service with platform-specific blockingfocuser-cli— Command-line interfacefocuser-ui— Tauri GUI (future)
- Docs:
docs/contains FEATURES.md, ARCHITECTURE.md, ROADMAP.md
- Edition: Rust 2024
- Error handling: Use
thiserrorfor library errors in common/core,anyhowin binaries (service/cli) - Async runtime: Tokio (multi-threaded)
- Logging:
tracingcrate with structured logging. Usetracing::instrumenton public functions. - Database: SQLite via
rusqlite. All migrations infocuser-core/src/db/migrations/. - Platform code: Gate with
#[cfg(target_os = "...")]infocuser-service/src/platform/. Common trait infocuser-common/src/platform.rs, implemented per OS. - Serialization:
serdefor all data structures that cross boundaries (IPC, DB, config). - IDs: UUID v4 for all entities (blocks, schedules, etc.)
- Time:
chronofor all date/time. Store as UTC in DB, convert to local for display.
- Crate names:
focuser-*(kebab-case) - Module names:
snake_case - Types:
PascalCase - Functions/methods:
snake_case - Constants:
SCREAMING_SNAKE_CASE - Error variants:
PascalCase, descriptive (e.g.,BlockNotFound,DatabaseError)
- Unit tests in the same file (
#[cfg(test)] mod tests) - Integration tests in
tests/directory per crate - Use
tempfilefor tests that need filesystem - Use
rusqlite::Connection::open_in_memory()for DB tests
cargo build # Build all crates
cargo run -p focuser-cli # Run CLI
cargo run -p focuser-service # Run service (needs admin/root)
cargo test --workspace # Run all tests
cargo clippy --workspace # Lint- Hosts file blocking first — simplest, works everywhere, no driver needed
- SQLite for storage — single file, no external DB, embedded with rusqlite
- IPC via named pipes (Windows) / Unix sockets (Linux/macOS) — fast, local-only
- Service runs as elevated/root — required for hosts file and process control
- CLI communicates with service over IPC — CLI never modifies system directly
- Modular platform traits — each OS implements
PlatformBlockertrait - Extension-ready architecture — browser extension support is deferred but the
integration points are built:
focuser-common/src/extension.rsdefines the full protocol (messages, rule sets, events)BlockEngine::compile_extension_rules()compiles active rules into extension formatBlockEngine::has_extension_only_rules()detects when extension is needed- IPC has
GetExtensionRules,ExtensionEvent,GetCapabilitiesvariants BlockingCapabilitiestracks what blocking methods are available at runtime- When extension is added: create
focuser-nativecrate (Native Messaging host binary) that bridges stdin/stdout JSON ↔ IPC, and the browser extension consumesExtensionRuleSet
Before EVERY commit and push, run ALL three checks and ensure they pass:
cargo fmt --all -- --check # Formatting — must pass
cargo clippy --workspace -- -D warnings # Linting — zero errors
cargo test --workspace # Tests — all must passIf any check fails, fix the issue BEFORE committing. Never push code that fails these checks. This prevents CI failures on GitHub Actions.
- Don't use
unwrap()orexpect()in library code — propagate errors - Don't use
unsafeunless absolutely necessary and document why - Don't add dependencies without justification
- Don't put platform-specific code outside of
platform/modules - Don't store passwords in plaintext — use argon2 hashing