originv0.2.0

Creating a module

Add a feature area: service, registration, settings, events, commands and a test that needs no Tauri.

A module is a feature area: Inbox, Projects, Traffic, Health. Use examples/demo/src-tauri/src/pulse.rs as the worked example.

1. Write the service

The service holds the logic and takes everything it needs from Platform:

pub struct InboxService {
    platform: Platform,
}

It must not open a database, call an OS API, or hold an AppHandle. If it needs something the platform does not offer, that is a platform gap — raise it, do not work around it.

2. Register it

impl ApplicationModule for InboxModule {
    fn id(&self) -> &'static str { "inbox" }

    fn register(&self, registry: &mut ModuleRegistry) -> Result<()> {
        registry.provide(Arc::new(InboxService::new(registry.platform().clone())));
        Ok(())
    }
}

Registration failure is a startup failure with the module named in the error — a misconfigured module must not start half-working.

3. Declare settings next to the code that reads them

const REFRESH_MINUTES: Setting<u32> = Setting::new("inbox.refresh_minutes", || 5);

Key and default in one place. A stored value that no longer decodes falls back to the default with a warning rather than blocking startup.

4. Publish events for anything others may react to

platform.events.publish(PlatformEvent::AlertRaised(AlertRaised { alert, deduplicated }));

The module does not decide whether that becomes a notification, a UI update or nothing.

5. Add commands and a client wrapper

A command resolves state, delegates and translates errors — no logic:

#[tauri::command]
pub async fn inbox_list(state: State<'_, OriginState>) -> Result<Vec<Item>, CommandError> {
    Ok(state.application().require::<InboxService>()?.list().await?)
}

Then a typed wrapper in the product’s client slice, so no component ever calls command() with a raw string:

export const inbox = {
  list: () => command<Item[]>("inbox_list"),
};

6. Test it without Tauri

Build an in-memory application, register the module, assert on behaviour. If you cannot, something in the module knows too much about the host.

Before you promote anything to crates/

Read ADR-0009. A feature stays in the product until a genuinely neutral abstraction has emerged — in practice at the third occurrence, not the first similarity.

Edit this page on GitHub · Docs for v0.2.0