                    for (let labelNum = 0; labelNum < 2; labelNum++) {
                        let inLabel = true;
                        for (let k = j + 1; k < rowNodes.length; k++) {
                            if (isLabelEnd(rowNodes[k], arrowChar)) {
                                inLabel = false;
                                j = k;
                                break;
                            }
                            if (isStartOfArrow(rowNodes[k])) {
                                throw new ParseError("Missing a " + arrowChar +
                                " character to complete a CD arrow.", rowNodes[k]);
                            }

                            labels[labelNum].body.push(rowNodes[k]);
                        }
                        if (inLabel) {
                            // isLabelEnd never returned a true.
                            throw new ParseError("Missing a " + arrowChar +
                                " character to complete a CD arrow.", rowNodes[j]);
                        }
                    }
                } else {
                    throw new ParseError(`Expected one of "<>AV=|." after @`,
                        rowNodes[j]);
                }

                // Now join the arrow to its labels.
                const arrow: AnyParseNode = cdArrow(arrowChar, labels, parser);

                // Wrap the arrow in  ParseNode<"styling">.
                // This is done to match parseArray() behavior.
                const wrappedArrow: ParseNode<"styling"> = {
                    type: "styling",
                    body: [arrow],
                    mode: "math",
                    style: "display", // CD is always displaystyle.
                };
                row.push(wrappedArrow);
                // In CD's syntax, cells are implicit. That is, everything that
                // is not an arrow gets collected into a cell. So create an empty
                // cell now. It will collect upcoming parseNodes.
                cell = newCell();
            }
        }
        if (i % 2 === 0) {
            // Even-numbered rows consist of: cell, arrow, cell, arrow, ... cell
            // The last cell is not yet pushed into `row`, so:
            row.push(cell);
        } else {
            // Odd-numbered rows consist of: vert arrow, empty cell, ... vert arrow
            // Remove the empty cell that was placed at the beginning of `row`.
61:    "@babel/eslint-parser": "^7.28.6",
75:    "@typescript-eslint/eslint-plugin": "^8.56.0",
76:    "@typescript-eslint/parser": "^8.56.0",
88:    "eslint": "^8.23.0",
89:    "eslint-import-resolver-webpack": "^0.13.10",
90:    "eslint-plugin-actions": "^2.0.0",
91:    "eslint-plugin-import": "^2.26.0",
92:    "eslint-plugin-react": "^7.31.1",
99:    "jest": "^30.2.0",
100:    "jest-diff": "^30.2.0",
101:    "jest-environment-jsdom": "^30.2.0",
102:    "jest-matcher-utils": "^30.2.0",
103:    "jest-message-util": "^30.2.0",
104:    "jest-serializer-html": "^7.1.0",
125:    "stylelint": "^14.11.0",
126:    "stylelint-config-standard": "^28.0.0",
127:    "stylelint-scss": "^6.3.2",
139:    "test": "yarn test:lint && yarn test:ts && yarn test:jest",
140:    "test:lint": "yarn test:lint:js && yarn test:lint:css",
141:    "test:lint:js": "eslint .",
142:    "test:lint:css": "stylelint src/styles/katex.scss static/main.css website/static/**/*.css",
143:    "test:jest": "jest",
144:    "test:jest:watch": "jest --watch",
145:    "test:jest:update": "jest --updateSnapshot",
146:    "test:jest:coverage": "jest --coverage",
147:    "test:screenshots": "yarn test:screenshots:update --verify",
148:    "test:screenshots:update": "dockers/screenshotter/screenshotter.sh",
149:    "test:perf": "NODE_ENV=test node -e \"require('@babel/register')({extensions:['.js','.ts']}); require('./test/perf-test.ts')\"",
150:    "test:ts": "tsc --noEmit",
151:    "test:build": "node test/verify-build.js",
170:      "pre-commit": "yarn test:lint"
173:  "jest": {
184:      "jest-serializer-html"
186:    "testMatch": [
189:    "testEnvironmentOptions": {
