{"record":{"id":"bd418f7dd517da2a","repo":"iOfficeAI/OfficeCLI","slug":"unknown-color-transform-name-valid-lummod-l","errorCode":null,"errorMessage":"Unknown color transform '{name}'. Valid: lumMod, lumOff, shade, tint, satMod, satOff, hueMod, hueOff, alpha.","messagePattern":"Unknown color transform '(.+?)'\\. Valid: lumMod, lumOff, shade, tint, satMod, satOff, hueMod, hueOff, alpha\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"warning","filePath":"src/officecli/Core/DrawingColorBuilder.cs","lineNumber":114,"sourceCode":"    {\n        var result = new List<(string Name, int Val)>();\n        foreach (var token in chain.Split('+', StringSplitOptions.RemoveEmptyEntries))\n        {\n            // Two accepted forms:\n            //   \"lumMod75\"        — Get's canonical round-trip form, percent 0..100\n            //   \"lumMod=75000\"    — raw OOXML percentage 0..100000\n            //                       (matches the literal a:lumMod@val attribute,\n            //                        what users see in PowerPoint XML / docs)\n            // Both end up encoded as @val=\"75000\" on the OOXML child. The name is\n            // a leading run of letters; the remainder is '='?<signed-int>. Scan the\n            // name by letters (not \"first digit\") so a leading '-' on the value\n            // stays with the value instead of being folded into the name.\n            int i = 0;\n            while (i < token.Length && char.IsLetter(token[i])) i++;\n            if (i == 0 || i == token.Length) continue;\n            var name = token.Substring(0, i);\n            if (!KnownTransforms.Contains(name))\n                throw new ArgumentException(\n                    $\"Unknown color transform '{name}'. Valid: lumMod, lumOff, shade, tint, satMod, satOff, hueMod, hueOff, alpha.\");\n            bool eqForm = token[i] == '=';\n            string numText = eqForm ? token.Substring(i + 1) : token.Substring(i);\n            if (!int.TryParse(numText, out var raw))\n                throw new ArgumentException(\n                    $\"Invalid color transform '{token}': value must be an integer.\");\n            // OOXML splits the transforms into two schema types:\n            //   shade / tint / alpha  → ST_PositiveFixedPercentage (0..100%),\n            //                           negatives forbidden.\n            //   lumMod / lumOff / satMod / satOff / hueMod / hueOff\n            //                         → ST_Percentage: SIGNED and may exceed 100%\n            //                           (e.g. satMod200% to over-saturate, or\n            //                           satOff-10% to desaturate). Rejecting\n            //                           negatives wrongly aborted replay of the\n            //                           round-trip form Get emits for these\n            //                           (satOff val=\"-10000\" → \"satOff-10\").\n            // Only the fixed-percentage family stays clamped 0..100.\n            bool fixedPct = name.ToLowerInvariant() is \"shade\" or \"tint\" or \"alpha\";","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/DrawingColorBuilder.cs#L96-L132","documentation":"ParseColorTransformSuffix scans each token's leading letter run as the transform name and checks it against KnownTransforms ({lumMod, lumOff, shade, tint, satMod, satOff, hueMod, hueOff, alpha}, case-insensitive). An unrecognized name is rejected rather than silently dropped, so a typo doesn't produce a no-op color. The valid set is listed in the message.","triggerScenarios":"A color suffix token like 'lumMod50%' where the letter prefix isn't one of the nine known transforms — e.g. 'brightness50', 'mod50', 'lum50', a misspelled 'lummode'. The name is everything up to the first non-letter.","commonSituations":"User writes a color transform using a PowerPoint-UI name or a different library's vocabulary instead of the OOXML transform names; typo in a round-tripped value; copying a transform name from docs that uses a different casing/spelling.","solutions":["Use one of the nine valid names exactly: lumMod, lumOff, shade, tint, satMod, satOff, hueMod, hueOff, alpha (case-insensitive).","Check the spelling — 'lummode' vs 'lumMod', 'tintt' vs 'tint'.","Map the desired effect to the closest OOXML transform (e.g. 'brightness' -> lumMod/lumOff)."],"exampleFix":"// before — unsupported name\nfill=\"red lummode50\"\n\n// after — canonical OOXML name\nfill=\"red lumMod50\"","handlingStrategy":"validation","validationCode":"static readonly HashSet<string> ValidTransforms = new(StringComparer.OrdinalIgnoreCase)\n    { \"lumMod\",\"lumOff\",\"shade\",\"tint\",\"satMod\",\"satOff\",\"hueMod\",\"hueOff\",\"alpha\" };\nstatic bool IsValidTransformName(string token)\n{\n    int i = 0; while (i < token.Length && char.IsLetter(token[i])) i++;\n    return i > 0 && ValidTransforms.Contains(token.Substring(0, i));\n}","typeGuard":"static bool IsValidColorTransform(string token)\n{\n    int i = 0; while (i < token.Length && char.IsLetter(token[i])) i++;\n    if (i == 0 || i == token.Length) return false;\n    if (!ValidTransforms.Contains(token.Substring(0, i))) return false;\n    var rest = token[i] == '=' ? token.Substring(i + 1) : token.Substring(i);\n    return int.TryParse(rest, out _);\n}","tryCatchPattern":"try { DrawingColorBuilder.Build(color); }\ncatch (ArgumentException ex) when (ex.Message.StartsWith(\"Unknown color transform\", StringComparison.Ordinal))\n{ errors.Add(ex.Message); /* surface typo to user */ }","preventionTips":["Use the nine canonical OOXML transform names.","Validate the transform name set before building color suffixes.","Watch for casing/spelling drift from docs."],"tags":["color","transform","validation","user-input","ooxml"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}