{"record":{"id":"dd9611321c682c4b","repo":"santifer/career-ops","slug":"skipping-filename-a-url-sits-under-misplacedurl-join-while","errorCode":null,"errorMessage":"⚠️  Skipping ${filename}: a URL sits under \"${misplacedUrl.join('\", \"')}\" while the \"url\" cell is absent — the optional cells are shifted; write an empty cell for each optional value you omit","messagePattern":"⚠️  Skipping (.+?): a URL sits under \"(.+?)\" while the \"url\" cell is absent — the optional cells are shifted; write an empty cell for each optional value you omit","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"merge-tracker.mjs","lineNumber":861,"sourceCode":"  if (blankRequiredCells.length) {\n    console.warn(`⚠️  Skipping ${filename}: required cell(s) present but empty: ${blankRequiredCells.join(', ')} — use the documented sentinel (— / N/A) rather than a blank cell`);\n    return null;\n  }\n\n  // The optional tail can only be trusted when a short row's missing cells are\n  // genuinely its LAST ones. Position cannot tell \"notes omitted, url written\"\n  // from \"notes written, url omitted\" — both are one cell short — so the one\n  // TYPED optional column is the corroboration. A cell that IS a URL, sitting\n  // under a label that is not `url` while the `url` label has no cell at all,\n  // means every optional value is one column left of its label. Merging that\n  // stores the posting URL as the row's NOTES and leaves the dedup key empty,\n  // which is precisely the mapping the header exists to protect.\n  if (map.url != null && shortLabels.includes('url')) {\n    const misplacedUrl = Object.entries(map)\n      .filter(([k, i]) => k !== 'url' && i < parts.length && /^https?:\\/\\/\\S*$/i.test(String(parts[i] ?? '').trim()))\n      .map(([k]) => k);\n    if (misplacedUrl.length) {\n      console.warn(`⚠️  Skipping ${filename}: a URL sits under \"${misplacedUrl.join('\", \"')}\" while the \"url\" cell is absent — the optional cells are shifted; write an empty cell for each optional value you omit`);\n      return null;\n    }\n  }\n\n  // Write-canonical: the tracker stores scores unbolded (verify-pipeline\n  // rejects bold scores), so strip any markdown bold from the incoming cell.\n  const score = at('score').replace(/\\*\\*/g, '').trim();\n  if (!looksLikeScoreCell(score)) {\n    // Corroboration, not disambiguation: the header already said which column\n    // this is, so this can no longer pick an order — it only asks whether the\n    // labelled cell holds what the label promises. It stays a hard gate because\n    // the value it catches is an emitter that wrote its VALUES in one order and\n    // its LABELS in another, which is the silent swap in a new costume. The\n    // headerless path already refuses a score cell of this shape (neither cell\n    // score-like → undecidable), so this is the same strictness, not new.\n    console.warn(`⚠️  Skipping ${filename}: the column labelled \"score\" reads \"${score}\", which is not a score or a documented sentinel (X.X/5, N/A, —, -) — check the header labels match the value order`);\n    return null;\n  }","sourceCodeStart":843,"sourceCodeEnd":879,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/merge-tracker.mjs#L843-L879","documentation":"When a headed row is one or more cells short AND its missing labels include the optional 'url' column, the parser checks for a typed corroborating signal: an http(s):// value sitting under a different label while the 'url' label has no cell. That proves the optional cells are shifted one or more columns left of their labels — the URL would be stored as notes and the dedup key left empty. The file is skipped rather than merged with the wrong mapping.","triggerScenarios":"parseHeadedAddition (merge-tracker.mjs:856-863): map.url exists in the header, shortLabels includes 'url' (the row has fewer cells than the url column index), and some non-url-labelled cell matches /^https?:\\/\\/\\S*$/i. E.g. a 9-cell row for a 10-label header where notes and url were both written but location was omitted without an empty cell.","commonSituations":"A writer dropped an interior optional cell (location or via) instead of writing an empty cell for it, sliding notes/url left; headerless-style positional writing was used against a headed template; a spreadsheet export skipped empty cells entirely.","solutions":["Rewrite the data row so it has exactly one cell per header label, writing an empty cell (consecutive tabs) for every optional value you omit — the shifted URL then lands under 'url'.","Alternatively drop the 'url' column from the header if you never send URLs, or use a fully-populated 10-cell row.","Check the emitter's field ordering: it must emit cells in exactly the header's label order with placeholders for skipped optionals.","After fixing, re-run merge-tracker.mjs and confirm the row merges with the URL in the tracker's URL column."],"exampleFix":"// before (location omitted without empty cell — notes/url shifted left)\nnum\tdate\tcompany\trole\tscore\tstatus\tpdf\treport\tnotes\tvia\tlocation\turl\n42\t...\tAcme\tBackend\t4.2/5\tEvaluated\t❌\t[042](reports/042.md)\tRemote friendly\thttps://jobs.acme.com/123\t\n\n// after (empty cell for the omitted optional)\n42\t...\tAcme\tBackend\t4.2/5\tEvaluated\t❌\t[042](reports/042.md)\tRemote friendly\t\t\thttps://jobs.acme.com/123","handlingStrategy":"validation","validationCode":"const header = lines[0].split('\\t');\nconst cells = lines[1].split('\\t');\nif (cells.length !== header.length) {\n  throw new Error(`${file}: ${header.length - cells.length} cell(s) short — write an empty cell (extra tab) for every omitted optional value`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always write an empty cell (consecutive tabs) for an omitted optional value — never skip the cell.","Build the row programmatically from the header labels array so the length always matches.","Never fall back to positional writing under a headed template; the header defines the order.","Check that spreadsheet/CSV exports preserve empty cells instead of collapsing them."],"tags":["tsv","parsing","merge-tracker","column-shift","url"],"backgroundTag":"shape-mismatch","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}