When to use it
The product does work that can take seconds or hours: reading a folder tree,
importing files, talking to a device. The steps use an acme.import job in a
crates/acme-import crate as the example; examples/rescuewerk shows the same
pattern with a folder scan.
Rules to keep
- Product code lives in product crates that depend on
app-api; the template crates (crates/app-*) never depend on a product. - A command never waits for a job.
start_jobreturns the id at once and the UI follows the events;wait_for_jobis only for the CLI and tests. - The handler calls
ctx.checkpoint()?at least once per unit of work: pause and cancel take effect only there. - Failures and warnings are stable
RW_*codes, translated in the three locales. Never put sentences or user paths in a code. - A path chosen in the UI goes through
validatePathbefore it becomes a job parameter.
Steps
- Create
crates/acme-import/with aCargo.tomllikeexamples/rescuewerk/crates/rescuewerk-scan/Cargo.toml(workspace fields,app-apiandserde_json,app-configfor tests). The workspace already includescrates/*. Keep product crates undercrates/: the catalog test inpackages/i18n/src/catalog.test.tschecks theRW_*codes found incrates/andapps/, not inexamples/. - Implement
JobHandleras inrescuewerk-scan/src/lib.rsanddocs/job-engine.md: read and checkctx.params(), failing withJobError::failed("RW_IMPORT_...")on invalid input; callctx.checkpoint()?in the loop; reportctx.progress(done, total)(Nonewhile the total is unknown, the final value at the end); usectx.warnfor items that can be skipped. Export the kind as a constant, for examplepub const IMPORT_JOB_KIND: &str = "acme.import";. - Test the job in
crates/acme-import/tests/throughAppApi, asrescuewerk-scan/tests/scan.rsdoes: build the APIin_memory()with.job_handler(IMPORT_JOB_KIND, Arc::new(ImportJob)), start it, wait withwait_for_joband check the final state,done/totaland theerror_codefor invalid input. Use files underfixtures/, never paths of the developer's machine. - Register the handler in the shell: add
acme-import = { path = "crates/acme-import" }to[workspace.dependencies]inCargo.tomlandacme-import.workspace = truetoapps/desktop/src-tauri/Cargo.toml, then call.job_handler(...)on the builder inapps/desktop/src-tauri/src/main.rs. Do the same inapps/cliif the CLI must run it, and commit the updatedCargo.lock(CI runscargo fetch --locked). No new Tauri command is needed:start_job,pause_job,resume_jobandcancel_jobalready exist. - In the UI build the request in a service of the feature, like
sampleJobRequestinapps/desktop/src/features/jobs/services/job-requests.ts: validate and clamp the parameters, and resolve paths withvalidatePathfirst. Start it withstartJoband follow it through the jobs queries that the events already update. - Teach
MockNativeClientinpackages/native-client/src/mock.tsthe new kind (it rejects unknown kinds withRW_JOB_KIND_UNKNOWN), sopnpm devand the component tests work without Rust. - Add every new
RW_*code topackages/i18n/locales/*/errors.jsonin de-DE, en and it-IT, and the UI texts tocommon.json.
Done when
- the verification commands pass;
- the job can be paused, resumed and cancelled while it runs, and reaches
COMPLETEDorFAILEDwith a translated code; - the integration test runs through
AppApiwithout network or machine paths; - every new code exists in the three locales.
