{"record":{"id":"bb2f6dc717abb277","repo":"pbakaus/impeccable","slug":"usage-font-match-mjs-measure-text-region-id","errorCode":null,"errorMessage":"usage: font-match.mjs --measure <text-region-id> | --rank <text-region-id> [--candidates \"Family:700,Family2:400,...\"] [--text \"...\"] [--transform uppercase] [--category sans,serif,display,handwriting,mono]\n","messagePattern":"usage: font-match\\.mjs --measure <text-region-id> \\| --rank <text-region-id> \\[--candidates \"Family:700,Family2:400,\\.\\.\\.\"\\] \\[--text \"\\.\\.\\.\"\\] \\[--transform uppercase\\] \\[--category sans,serif,display,handwriting,mono\\]\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/comp-verbs/src/font_match.rs","lineNumber":430,"sourceCode":"fn write_spec(io: &Io, spec_path: &str, spec: &Value) {\n    let out = resolve(io, spec_path);\n    if let Some(parent) = out.parent() {\n        let _ = std::fs::create_dir_all(parent);\n    }\n    let _ = std::fs::write(out, util::json_pretty(spec));\n}\n\n// ---- CLI -------------------------------------------------------------------\n\n/// `impeccable font-match --measure <id> | --rank <id> ...`\npub fn run(argv: &[String], io: &mut Io, renderer: &mut dyn FontRenderer) -> i32 {\n    let spec_path = arg_or(argv, \"spec\", SPEC_PATH).to_string();\n    let mut spec = load_spec(&resolve(io, &spec_path));\n    let measure_id = arg(argv, \"measure\");\n    let rank_id = arg(argv, \"rank\");\n    let id = measure_id.or(rank_id);\n    let Some(id) = id else {\n        io.err(\"usage: font-match.mjs --measure <text-region-id> | --rank <text-region-id> [--candidates \\\"Family:700,Family2:400,...\\\"] [--text \\\"...\\\"] [--transform uppercase] [--category sans,serif,display,handwriting,mono]\\n\");\n        return 1;\n    };\n    let Some(spec_val) = spec.as_mut() else {\n        io.err(&format!(\"font-match: no spec at {spec_path}; run comp-spec.mjs first\\n\"));\n        return 1;\n    };\n    let region = spec_val\n        .get(\"regions\")\n        .and_then(Value::as_array)\n        .and_then(|a| a.iter().find(|r| r.get(\"id\").and_then(Value::as_str) == Some(id)))\n        .cloned();\n    let Some(region) = region else {\n        let ids = spec_val\n            .get(\"regions\")\n            .and_then(Value::as_array)\n            .map(|a| a.iter().filter_map(|r| r.get(\"id\").and_then(Value::as_str)).collect::<Vec<_>>().join(\", \"))\n            .unwrap_or_default();\n        io.err(&format!(\"font-match: no region {id}; ids: {ids}\\n\"));","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/pbakaus/impeccable/blob/2bc2879276c1f321a53c4ca99d3371e411329b52/crates/comp-verbs/src/font_match.rs#L412-L448","documentation":"The `font-match.mjs` verb requires exactly one mode selector: `--measure <text-region-id>` or `--rank <text-region-id>`. When neither flag (or no id value) is supplied, the tool prints this usage line to stderr and exits 1 without touching the spec. It is a pure argument-validation guard at the top of `run`.","triggerScenarios":"Calling `font-match.mjs` with no flags at all; passing `--measure` or `--rank` without an id value; passing only auxiliary flags like `--candidates`/`--text`/`--transform`/`--category` without a mode+id pair.","commonSituations":"Scripting the tool and forgetting the region id; copying an example that used a placeholder id; wiring the wrong argv array so the mode flag never reaches `run`; typos like `--mesure` leaving both `arg()` lookups empty.","solutions":["Add exactly one of `--measure <text-region-id>` or `--rank <text-region-id>` to the invocation.","Ensure the flag has a non-empty value (`--measure hero-title`, not a bare `--measure`).","Check the id exists in the spec first (`comp-spec` output regions array) so the corrected call doesn't then hit the no-region error.","Fix any flag typos that silently leave `measure_id` and `rank_id` unset."],"exampleFix":"// before\nnode font-match.mjs --candidates \"Inter:400,Söhne:700\"\n// after\nnode font-match.mjs --measure hero-title --candidates \"Inter:400,Söhne:700\"","handlingStrategy":"validation","validationCode":"const mode = argv.includes('--measure') ? 'measure' : argv.includes('--rank') ? 'rank' : null;\nconst id = mode ? argv[argv.indexOf('--' + mode) + 1] : null;\nif (!mode || !id) {\n  console.error('font-match requires --measure <id> or --rank <id>');\n  process.exit(1);\n}","typeGuard":"function hasModeAndId(argv) {\n  const i = argv.findIndex(a => a === '--measure' || a === '--rank');\n  return i !== -1 && typeof argv[i + 1] === 'string' && argv[i + 1].length > 0;\n}","tryCatchPattern":null,"preventionTips":["Wrap font-match invocations in a helper that enforces the mode+id pair.","Check flag spelling carefully — an unrecognized mode flag silently leaves both lookups empty.","Keep the usage line from the error in your script docs."],"tags":["cli","usage","missing-flag"],"backgroundTag":"missing-required-flag","analyzedSha":"2bc2879276c1f321a53c4ca99d3371e411329b52","analyzedAt":"2026-09-08T04:51:14.109Z","contentChangedAt":"2026-09-08T04:51:14.109Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}