Feature Description
Summary
Promote RapidRAW's existing internal command surface into a documented, addressable interface, and version the sidecar format that already acts as its public API. No new models, no LLM, no cloud. This is plumbing and specification work that makes the app scriptable by anything, not just by the bundled webview.
Why now
#1418 asked for a headless CLI export and it shipped. LaunchRequest::HeadlessExport exists, run_headless_export is wired in at lib.rs:2056, and --adjustments <path> (launch_request.rs:74) already lets an external process hand RapidRAW a complete edit and get a rendered file back with no window open.
That is a bigger change than the issue it came from. It established that the app does not strictly need a human at the GUI. Right now that capability exists for exactly one verb, parsed by hand. This proposal is about generalising it.
What already exists
Four things make this much smaller than it sounds:
- The app is already a command bus. 102
#[tauri::command] functions across src-tauri/src/, all serde-serialisable, all IPC-shaped. They are simply unreachable from anything except the webview.
- The edit contract already exists and is complete.
Adjustments (src/utils/adjustments.ts) has 73 top-level fields, more once nested structures are expanded (HSL alone is 8 colour bands times 3 properties). Masks are declarative too: SubMask is {id, type, invert, mode, opacity, parameters, visible}. Anything a human can express with the sliders is already expressible as JSON. There is no hidden binary state.
- Headless rendering is proven. Offscreen GPU context, CPU readback, sidecar hydration for images that are not open.
- Sidecars are already the integration point. Third-party tooling is being written against
.rrdata today. It is a public API whether or not it is documented as one.
Phase 0: an agent interface
Generalise #1418 from one hardcoded verb to the whole command surface.
- A real CLI dispatch layer.
parse_launch_args currently hand-rolls flag parsing for a single export subcommand. Replace it with dispatch over the command registry so export becomes one verb among many rather than a special case.
- An opt-in local JSON-RPC endpoint over a Unix socket / named pipe. Off by default, local-only, no network bind.
- Machine-readable command discovery, so an external tool can enumerate what the app can do rather than hardcoding a list that silently rots.
Value: the difference between "scriptable if someone writes a bespoke integration" and "operable by tools nobody has written yet." Batch pipelines, CI-rendered contact sheets, and automated import/cull/export flows all become possible without touching the UI.
Phase 1: version and publish the edit contract
- Add
schemaVersion to .rrdata.
- Publish a JSON Schema for
Adjustments, SubMask, and the mask parameters variants.
- Guarantee and test round-trip stability: unknown keys preserved, defaults explicit.
- Document the shallow-merge semantics presets already depend on (
presetType: "tool" partial versus "style" full).
Value: without this, every external writer is guessing and every internal refactor breaks them silently. With it, third-party tooling becomes safe to build, which is what makes Phase 0 worth anything. It also protects existing users: right now a preset written against today's field names has no version marker to fail loudly against.
Non-goals
- No LLM, no reasoning layer, no "auto-enhance" in this proposal. Deliberately scoped out.
- No telemetry, no network calls, nothing leaves the machine.
- No change to the rendering pipeline or to any existing behaviour. Both phases are additive.
- No change to the paid cloud roadmap. This is orthogonal to generative AI work.
Sequencing and risk
| Phase |
Scope |
Depends on |
Risk |
| 0 |
Command bus dispatch + CLI + discovery |
nothing |
low, mostly plumbing |
| 1 |
Sidecar schema, versioning, round-trip tests |
nothing |
low |
The two are independent and can land in either order. Phase 1 is arguably the more valuable of the two on its own, since it protects the format that users' entire edit histories live in.
What I can contribute
I am happy to take both phases end to end, including tests, and to split them into separate PRs. I would rather agree the shape before writing code than send an unsolicited large diff.
Open questions
- Is an opt-in local socket interface acceptable in principle, or do you want the webview boundary to stay hard? If the latter, Phase 0 can be CLI-only and still delivers most of the value.
- JSON Schema committed in-repo, or generated from the Rust and TypeScript types at build time?
- Any objection to
schemaVersion landing in .rrdata now, defaulted for existing files, so future format changes have something to negotiate against?
Additional Context
This is deliberately the small, boring half of a larger idea. There is a longer-term case for semantic culling and adjustment reasoning on top of the models already bundled (SAM, depth-anything, CLIP), but none of that is worth discussing until the interface and the format are stable. I would rather propose the groundwork on its own merits and revisit the rest separately.
Context for the request: I run a photography pipeline where import, culling and publishing are automated, with RapidRAW as the develop stage. #1418 removed the last manual bottleneck for a single-verb export. These two phases would remove the need for anyone to write a bespoke integration to do the same for anything else.
Feature Description
Summary
Promote RapidRAW's existing internal command surface into a documented, addressable interface, and version the sidecar format that already acts as its public API. No new models, no LLM, no cloud. This is plumbing and specification work that makes the app scriptable by anything, not just by the bundled webview.
Why now
#1418 asked for a headless CLI export and it shipped.
LaunchRequest::HeadlessExportexists,run_headless_exportis wired in atlib.rs:2056, and--adjustments <path>(launch_request.rs:74) already lets an external process hand RapidRAW a complete edit and get a rendered file back with no window open.That is a bigger change than the issue it came from. It established that the app does not strictly need a human at the GUI. Right now that capability exists for exactly one verb, parsed by hand. This proposal is about generalising it.
What already exists
Four things make this much smaller than it sounds:
#[tauri::command]functions acrosssrc-tauri/src/, all serde-serialisable, all IPC-shaped. They are simply unreachable from anything except the webview.Adjustments(src/utils/adjustments.ts) has 73 top-level fields, more once nested structures are expanded (HSL alone is 8 colour bands times 3 properties). Masks are declarative too:SubMaskis{id, type, invert, mode, opacity, parameters, visible}. Anything a human can express with the sliders is already expressible as JSON. There is no hidden binary state..rrdatatoday. It is a public API whether or not it is documented as one.Phase 0: an agent interface
Generalise #1418 from one hardcoded verb to the whole command surface.
parse_launch_argscurrently hand-rolls flag parsing for a singleexportsubcommand. Replace it with dispatch over the command registry soexportbecomes one verb among many rather than a special case.Value: the difference between "scriptable if someone writes a bespoke integration" and "operable by tools nobody has written yet." Batch pipelines, CI-rendered contact sheets, and automated import/cull/export flows all become possible without touching the UI.
Phase 1: version and publish the edit contract
schemaVersionto.rrdata.Adjustments,SubMask, and the maskparametersvariants.presetType: "tool"partial versus"style"full).Value: without this, every external writer is guessing and every internal refactor breaks them silently. With it, third-party tooling becomes safe to build, which is what makes Phase 0 worth anything. It also protects existing users: right now a preset written against today's field names has no version marker to fail loudly against.
Non-goals
Sequencing and risk
The two are independent and can land in either order. Phase 1 is arguably the more valuable of the two on its own, since it protects the format that users' entire edit histories live in.
What I can contribute
I am happy to take both phases end to end, including tests, and to split them into separate PRs. I would rather agree the shape before writing code than send an unsolicited large diff.
Open questions
schemaVersionlanding in.rrdatanow, defaulted for existing files, so future format changes have something to negotiate against?Additional Context
This is deliberately the small, boring half of a larger idea. There is a longer-term case for semantic culling and adjustment reasoning on top of the models already bundled (SAM, depth-anything, CLIP), but none of that is worth discussing until the interface and the format are stable. I would rather propose the groundwork on its own merits and revisit the rest separately.
Context for the request: I run a photography pipeline where import, culling and publishing are automated, with RapidRAW as the develop stage. #1418 removed the last manual bottleneck for a single-verb export. These two phases would remove the need for anyone to write a bespoke integration to do the same for anything else.