Problem
vcad-kernel-cost::Material::catalog() lives in Rust and is the source of truth for material density, price, machine rate, and process compatibility. But packages/app/src/stores/output-store.ts::MATERIAL_MAPPINGS (added on the v1 DFM branch) hardcodes a parallel TS table:
export const MATERIAL_MAPPINGS: Record<MaterialType, MaterialMapping> = {
pla: { process: "fdm", catalogName: "PLA", display: { ... } },
aluminum: { process: "cnc_3axis", catalogName: "Aluminum 6061", display: { ... } },
steel: { process: "cnc_3axis", catalogName: "Steel 1018", display: { ... } },
};
Adding a new QuotePanel material now means editing both Rust (Material::catalog()) and TS (MATERIAL_MAPPINGS) and keeping them in lockstep. The display fields (lead time, friendly name) are arbitrary marketing strings that have no business being in TS specifically — they belong with the catalog entry.
Proposed approach
- Extend
vcad-kernel-cost::Material with optional UI metadata:
pub struct Material {
// existing physics + cost fields …
/// Friendly display name shown in panels.
#[serde(default)]
pub display_name: String,
/// Typical lead time in days for marketplace quoting.
#[serde(default)]
pub lead_time_days: u32,
}
- Add a
Material::catalog_for(process: Process) -> Vec<Material> filter helper.
- Expose via WASM:
get_material_catalog(process: &str) -> JsValue.
- QuotePanel calls
getMaterialCatalog("fdm" | "cnc_3axis" | …) once and renders whatever the kernel returns. MATERIAL_MAPPINGS and MaterialType go away.
Why now
The catalog is going to grow (high-temp resins, more steels, exotic alloys for casting). Each new entry currently demands two PRs in lockstep. Before that surface area expands, fix the source-of-truth.
Side benefit
The MCP dfm_check and estimate_cost tools could accept any catalog material by name without a hand-maintained enum on the agent side.
Acceptance criteria
- Adding a material requires editing only
vcad-kernel-cost.
MATERIAL_MAPPINGS and MaterialType deleted from output-store.ts.
- QuotePanel renders the same set of options it does today, populated from the kernel.
- The 11 catalog entries (PLA, PETG, ABS, TPU, SLA Resin, Aluminum 6061, Steel 1018, Brass C360, Polycarbonate, Cast Aluminum A356, Cast Iron) all carry display + lead-time metadata.
References
crates/vcad-kernel-cost/src/lib.rs::Material::catalog — current Rust catalog
packages/app/src/stores/output-store.ts — current TS shadow table
packages/app/src/components/QuotePanel.tsx::MATERIALS — consumer that becomes async-fetched
Problem
vcad-kernel-cost::Material::catalog()lives in Rust and is the source of truth for material density, price, machine rate, and process compatibility. Butpackages/app/src/stores/output-store.ts::MATERIAL_MAPPINGS(added on the v1 DFM branch) hardcodes a parallel TS table:Adding a new QuotePanel material now means editing both Rust (
Material::catalog()) and TS (MATERIAL_MAPPINGS) and keeping them in lockstep. The display fields (lead time, friendly name) are arbitrary marketing strings that have no business being in TS specifically — they belong with the catalog entry.Proposed approach
vcad-kernel-cost::Materialwith optional UI metadata:Material::catalog_for(process: Process) -> Vec<Material>filter helper.get_material_catalog(process: &str) -> JsValue.getMaterialCatalog("fdm" | "cnc_3axis" | …)once and renders whatever the kernel returns.MATERIAL_MAPPINGSandMaterialTypego away.Why now
The catalog is going to grow (high-temp resins, more steels, exotic alloys for casting). Each new entry currently demands two PRs in lockstep. Before that surface area expands, fix the source-of-truth.
Side benefit
The MCP
dfm_checkandestimate_costtools could accept any catalog material by name without a hand-maintained enum on the agent side.Acceptance criteria
vcad-kernel-cost.MATERIAL_MAPPINGSandMaterialTypedeleted fromoutput-store.ts.References
crates/vcad-kernel-cost/src/lib.rs::Material::catalog— current Rust catalogpackages/app/src/stores/output-store.ts— current TS shadow tablepackages/app/src/components/QuotePanel.tsx::MATERIALS— consumer that becomes async-fetched