Problem
estimate_cost_for_process in crates/vcad-kernel-wasm/src/lib.rs (added on the v1 DFM branch) takes six positional parameters:
pub fn estimate_cost_for_process(
process: &str,
material_name: &str,
part_volume_mm3: f64,
stock_volume_mm3: f64,
qty: u32,
feature_count: u32,
) -> Result<JsValue, JsError>
with magic-value semantics: stock_volume_mm3 = 0 means "use 2× part volume", qty = 0 means "use rule-pack default". TS callers can't tell from the signature which params are optional or what the sentinel values mean — the engine wrapper at packages/engine/src/dfm.ts::estimateCost papers over this with a ?? 0 fallback per arg.
Same critique applies to Solid.runDfm(process, rule_pack_toml, root_node_id) — root_node_id = 0 is the "skip provenance" sentinel.
Proposed approach
Take a single struct argument deserialized via serde_wasm_bindgen::from_value:
#[derive(Deserialize)]
struct EstimateCostRequest {
process: String,
material: String,
part_volume_mm3: f64,
#[serde(default)]
stock_volume_mm3: Option<f64>,
#[serde(default)]
qty: Option<u32>,
#[serde(default)]
feature_count: Option<u32>,
}
#[wasm_bindgen]
pub fn estimate_cost_for_process(req: JsValue) -> Result<JsValue, JsError> {
let req: EstimateCostRequest = serde_wasm_bindgen::from_value(req)?;
// ...
}
Optionality becomes a first-class concept via Option<T>, the engine wrapper can omit fields cleanly, and the .d.ts generated by ts-rs (or the hand-written types in packages/engine/src/dfm.ts) reflects optionality directly.
Why now
The DFM PR introduced this pattern; cleaning it up before more callers latch onto the positional shape is much cheaper than later. Same fix applied to Solid.runDfm and run_dfm_on_brep_json keeps the new DFM surface consistent.
Acceptance criteria
estimate_cost_for_process, Solid.runDfm, and run_dfm_on_brep_json accept a single struct argument.
packages/engine/src/dfm.ts EstimateCostOptions / RunDfmOptions interfaces line up directly with the Rust structs (no ?? 0 paper-over).
- TS consumers stop having to know the sentinel values.
- The MCP tools and the QuotePanel keep working unchanged behaviorally.
References
crates/vcad-kernel-wasm/src/lib.rs — three new functions added on claude/dfm-tools-integration-HcsqQ: estimate_cost_for_process, run_dfm_on_brep_json, Solid.runDfm
packages/engine/src/dfm.ts::estimateCost — current TS wrapper that spreads positionals
- The existing
evaluateDocument WASM binding does take positional (docJson, skipClashDetection) args and is happy — so this isn't a hard rule across the crate, just the right one for cost/DFM where optionality is real
Problem
estimate_cost_for_processincrates/vcad-kernel-wasm/src/lib.rs(added on the v1 DFM branch) takes six positional parameters:with magic-value semantics:
stock_volume_mm3 = 0means "use 2× part volume",qty = 0means "use rule-pack default". TS callers can't tell from the signature which params are optional or what the sentinel values mean — the engine wrapper atpackages/engine/src/dfm.ts::estimateCostpapers over this with a?? 0fallback per arg.Same critique applies to
Solid.runDfm(process, rule_pack_toml, root_node_id)—root_node_id = 0is the "skip provenance" sentinel.Proposed approach
Take a single struct argument deserialized via
serde_wasm_bindgen::from_value:Optionality becomes a first-class concept via
Option<T>, the engine wrapper can omit fields cleanly, and the.d.tsgenerated by ts-rs (or the hand-written types inpackages/engine/src/dfm.ts) reflects optionality directly.Why now
The DFM PR introduced this pattern; cleaning it up before more callers latch onto the positional shape is much cheaper than later. Same fix applied to
Solid.runDfmandrun_dfm_on_brep_jsonkeeps the new DFM surface consistent.Acceptance criteria
estimate_cost_for_process,Solid.runDfm, andrun_dfm_on_brep_jsonaccept a single struct argument.packages/engine/src/dfm.tsEstimateCostOptions/RunDfmOptionsinterfaces line up directly with the Rust structs (no?? 0paper-over).References
crates/vcad-kernel-wasm/src/lib.rs— three new functions added onclaude/dfm-tools-integration-HcsqQ:estimate_cost_for_process,run_dfm_on_brep_json,Solid.runDfmpackages/engine/src/dfm.ts::estimateCost— current TS wrapper that spreads positionalsevaluateDocumentWASM binding does take positional(docJson, skipClashDetection)args and is happy — so this isn't a hard rule across the crate, just the right one for cost/DFM where optionality is real