When to use it
The UI needs an answer from the Rust core that comes back at once, for example
the size of the local database or the details of a chosen file. The steps use a
storage_info command as the example. Work that can take more than a moment
(reading a folder tree, network, devices) is a job: see the playbook "Add a
job handler".
Rules to keep
- The path is always UI →
packages/native-client→ Tauri command →crates/app-api→ core crates. The command only calls oneAppApimethod; no logic inapps/desktop/src-tauri. - Every command needs its own permission in
apps/desktop/src-tauri/capabilities/main-window.json. No global or wildcard permissions. - Types cross the boundary as DTOs from
crates/app-api/src/dto.rs; the TypeScript versions are generated, never written by hand. - Errors are
ApiErrorvalues with a stableRW_*code, never sentences. The UI translates the code. - A path that comes from the UI goes through
validate_pathbefore it is used.
Steps
Add the DTO to
crates/app-api/src/dto.rswith the same derives and attributes as the others (Serialize,Deserialize, thets-exportcfg_attrandrename_all = "camelCase"). Marku64fields withts(type = "number")asJobDtodoes:#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "ts-export", derive(TS), ts(export))] #[serde(rename_all = "camelCase")] pub struct StorageInfoDto { pub data_dir: String, #[cfg_attr(feature = "ts-export", ts(type = "number"))] pub database_bytes: u64, }Add the method to
AppApiincrates/app-api/src/api.rs, returningResult<StorageInfoDto, ApiError>. Build failures withApiError::new("RW_...", category, recoverable)and check disabled modules as the existing methods do. Test it incrates/app-api/tests/api.rswith theapi(config())helper, including the error case.Run
pnpm types:generate, export the new type frompackages/shared-types/src/index.tsand commit the files inpackages/shared-types/src/generated.pnpm types:checkfails if they are out of date.Add the command to the matching file in
apps/desktop/src-tauri/src/commands/(herefilesystem.rs), followingvalidate_path: it takesState<'_>and the arguments, and returnsstate.api.storage_info(). Register it ingenerate_handler!insrc-tauri/src/main.rs, declare"storage_info"inbuild.rsand grant"allow-storage-info"incapabilities/main-window.json.Add the method to the
NativeClientinterface inpackages/native-client/src/client.ts, toTauriNativeClientintauri.ts(call<StorageInfoDto>("storage_info"); JavaScript arguments are camelCase and Tauri maps them to snake_case) and toMockNativeClientinmock.tswith realistic data and the error scenarios. Test the mock inmock.test.ts.Read it in the UI with a hook under
apps/desktop/src/features/<feature>/hooks/, likeuseFeatures:useQuerywith a stable key anduseNativeClient(). Show errors withi18n.error.Add every new
RW_*code topackages/i18n/locales/*/errors.jsonand every UI text tocommon.json, in de-DE, en and it-IT. Check the layout in German.List the command in
docs/tauri-bridge.md.
Done when
- the verification commands pass, and
pnpm types:checkreports no difference; - the command has exactly one
allow-<name>entry in the capability and is registered inmain.rsandbuild.rs; - the mock implements the method, so
pnpm devworks without the Rust core; - every new code and text exists in the three locales.
