Device capabilities (parts)

A part is Day’s name for a headless platform capability: no UI, just functions whose implementation differs per operating system. Battery level, the clipboard, preference storage, sensors: the things every cross-platform app eventually needs and every platform spells differently.

Parts are ordinary crates. You add one to Cargo.toml, call plain functions, and the right platform code runs because each function’s body dispatches on #[cfg(target_os)]: IOKit on macOS, BatteryManager over JNI on Android, sysfs on Linux, Win32 on Windows. There is no plugin registry or runtime lookup; the target selects the implementation at compile time.

The catalog

CrateWhat it doesReference
day-part-batterycharge level and charging statebattery
day-part-clipboardread/write the system clipboard (text)clipboard
day-part-prefssmall key-value preference storage in the platform’s conventional locationprefs
day-part-networkconnectivity statusnetwork
day-part-deviceinfodevice model, OS versiondeviceinfo
day-part-sensorsaccelerometer and friends, as a live streamsensors
day-part-httpHTTP through each platform’s own networking stackhttp
day-part-permissionsask the OS for the camera, location, notifications … and declare them at build timepermissions
day-part-locationthe device’s position, once or as a live streamlocation
day-part-hapticshaptic feedbackhaptics

Using parts

The APIs are small on purpose. Some examples, verbatim from the crates:

// Battery
if let Some(b) = day_part_battery::status() {
    println!("{:?}, {:?}%", b.state, b.percent());   // Charging, Some(80)
}

// Clipboard
day_part_clipboard::set_text("hello");
let text = day_part_clipboard::get_text();           // Option<String>

// Preferences — strings in, strings out, stored where the platform expects
day_part_prefs::set("theme", "dark");
let theme = day_part_prefs::get("theme");            // Option<String>

Wiring a part into UI is the usual reactive pattern (read into a signal, bind the signal):

let battery = Signal::new(day_part_battery::status());

column((
    label(move || match battery.get() {
        Some(b) => format!("{}%", b.percent().unwrap_or(0)),
        None => tr("battery-unknown").format(),
    }),
    button(tr("refresh")).action(move || battery.set(day_part_battery::status())),
))

Returns are Option/bool rather than panics: a desktop without a battery reports None, a denied clipboard read reports None, and your UI decides what that means. Check each part’s reference page for the per-platform support matrix; not every capability exists everywhere, and each function’s reference lists per-platform support rather than implying uniform coverage.

Writing your own

When you need a platform API Day doesn’t cover (Bluetooth, a payment SDK, notification badges), you write a part. The pattern scales from trivial to involved:

  • Pure-Rust platforms are a #[cfg] branch and a system crate (objc2 on Apple, windows on Windows, sysfs/D-Bus on Linux).
  • Android usually needs a small Java shim; a part can carry its own Java sources and Gradle dependencies, which day build aggregates into the app’s Gradle project automatically (no manual scaffold edits).
  • Permissions a part needs (say, vibration) are declared in the part’s metadata and merged into the Android manifest the same way.

day new part my-part scaffolds the whole shape with per-OS stubs. The part tutorial walks through a complete real example (a battery part with six platform implementations) and is the best template for your own.

One boundary worth respecting: parts are for headless capabilities. The moment your capability needs to render something, it’s a piece, and a different set of tools applies.