{"record":{"id":"1d16cc76e3f02c77","repo":"iOfficeAI/OfficeCLI","slug":"invalid-merge-ref-newrangeref-range-must-read","errorCode":null,"errorMessage":"Invalid merge ref '{newRangeRef}': range must read top-left to bottom-right. Pass the canonical orientation (e.g. 'A1:B2', not 'B2:A1').","messagePattern":"Invalid merge ref '(.+?)': range must read top-left to bottom-right\\. Pass the canonical orientation \\(e\\.g\\. 'A1:B2', not 'B2:A1'\\)\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Handlers/Excel/ExcelHandler.Helpers.Validation.cs","lineNumber":493,"sourceCode":"        // form, so callers passing a reversed pair almost certainly typo'd.\n        // Reject with a hint to swap, mirroring the orientation guard the\n        // sheetShift normalizer applies after the fact (ExcelHandler.Set.cs\n        // L1918) and matching how other range-bearing props (validation,\n        // table, autofilter) demand canonical orientation up front.\n        var colonIdx = refUpper.IndexOf(':');\n        if (colonIdx > 0)\n        {\n            var lhs = refUpper.Substring(0, colonIdx);\n            var rhs = refUpper.Substring(colonIdx + 1);\n            try\n            {\n                var (lCol, lRow) = ParseCellReference(lhs);\n                var (rCol, rRow) = ParseCellReference(rhs);\n                int lColIdx = ColumnNameToIndex(lCol);\n                int rColIdx = ColumnNameToIndex(rCol);\n                if (lColIdx > rColIdx || lRow > rRow)\n                {\n                    throw new ArgumentException(\n                        $\"Invalid merge ref '{newRangeRef}': range must read top-left to bottom-right. \" +\n                        $\"Pass the canonical orientation (e.g. 'A1:B2', not 'B2:A1').\");\n                }\n            }\n            catch (ArgumentException) { throw; }\n            catch { /* parse failure already handled by SingleMergeRefPattern above */ }\n        }\n    }\n\n    /// <summary>\n    /// Scan a formula body for Sheet-qualified refs (bare `Sheet1!A1`\n    /// or quoted `'My Data'!A1`) and return true if any referenced sheet\n    /// name does not exist in the current workbook. Used to suppress the\n    /// evaluator-based cachedValue fallback when cross-sheet refs point at\n    /// a removed sheet — Real Excel shows `#REF!` there; we should not\n    /// invent a \"0\".\n    /// </summary>\n    private bool FormulaReferencesMissingSheet(string formula)","sourceCodeStart":475,"sourceCodeEnd":511,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Handlers/Excel/ExcelHandler.Helpers.Validation.cs#L475-L511","documentation":"The merge range reads bottom-right to top-left (e.g. B2:A1, Z1:A1, A10:A1). Excel itself only writes the canonical top-left-to-bottom-right form, so a reversed pair is almost certainly a typo. The library parses both endpoints, converts columns via ColumnNameToIndex, and throws if the left column/row exceeds the right.","triggerScenarios":"Passing a range whose left cell is to the right of or below the right cell: lColIdx > rColIdx or lRow > rRow after ParseCellReference.","commonSituations":"UI selection made bottom-up; variables for start/end swapped; copy-paste from a tool that does not normalize orientation.","solutions":["Swap the two endpoints so the smaller column and smaller row come first.","Run a normalizer that sorts (col,row) pairs before building the ref string.","Generate refs from min/max of the two corners."],"exampleFix":"// before\nsheet.Merge(\"B2:A1\");\n\n// after\nsheet.Merge(\"A1:B2\");","handlingStrategy":"validation","validationCode":"static string CanonicalRange(string a, string b) {\n    var (ac, ar) = ParseCell(a); var (bc, br) = ParseCell(b);\n    var top = Math.Min(ColumnNameToIndex(ac), ColumnNameToIndex(bc));\n    var bot = Math.Max(ColumnNameToIndex(ac), ColumnNameToIndex(bc));\n    var left = Math.Min(ar, br); var right = Math.Max(ar, br);\n    return $\"{IndexToColumnName(top)}{left}:{IndexToColumnName(bot)}{right}\";\n}","typeGuard":null,"tryCatchPattern":"try { sheet.Merge(range); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"top-left to bottom-right\")) {\n    var parts = range.Split(':');\n    sheet.Merge($\"{parts[1]}:{parts[0]}\");\n}","preventionTips":["Always build ranges as (min corner):(max corner).","Sort endpoint indices before formatting the A1 string."],"tags":["excel","merge","cell-reference","orientation"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}