{"record":{"id":"caa6e6cc9f8ba92e","repo":"toon-format/toon","slug":"unexpected-indentation-inside-keyed-tabular-object","errorCode":null,"errorMessage":"Unexpected indentation inside keyed tabular object","messagePattern":"Unexpected indentation inside keyed tabular object","errorType":"exception","errorClass":"ToonDecodeError","httpStatus":null,"severity":"error","filePath":"packages/toon/src/decode/decoders.ts","lineNumber":362,"sourceCode":"  const seenEntryKeys = options.strict ? new Set<string>() : undefined\n  let entryCount = 0\n  let startLine: number | undefined\n  let endLine: number | undefined\n  let lastEntryLine: ParsedLine = headerLine\n\n  yield { type: 'startObject' }\n\n  // A keyed scope ends only by dedent or end of input, so every line at entry depth\n  // carrying an unquoted colon is an entry row.\n  while (true) {\n    const line = yield* peekLine(reader)\n    if (!line || line.depth <= baseDepth) {\n      break\n    }\n\n    if (line.depth > entryDepth) {\n      if (options.strict) {\n        throw new ToonDecodeError(\n          'Unexpected indentation inside keyed tabular object',\n          { line: line.lineNumber, source: line.raw },\n        )\n      }\n      yield* readLine(reader)\n      continue\n    }\n\n    if (findUnquotedChar(line.content, COLON) === -1) {\n      if (options.strict) {\n        throw new ToonDecodeError(\n          'Expected entry row inside keyed tabular object',\n          { line: line.lineNumber, source: line.raw },\n        )\n      }\n      yield* readLine(reader)\n      continue\n    }","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/toon-format/toon/blob/604eac266e35166bed6f5b9e3bd586e9c60a9330/packages/toon/src/decode/decoders.ts#L344-L380","documentation":"Inside a keyed tabular object (rows under a `[N]{...}:`-style header), entry rows must all sit at the same depth. In strict mode, a line indented deeper than the established entry depth is rejected because it would represent ambiguous nesting under a row. Non-strict mode skips such lines.","triggerScenarios":"decodeKeyedObject (invoked from decodeArrayFromHeader) peeks a line whose depth exceeds the entry depth while options.strict is true — e.g. one row accidentally indented one level further than its siblings.","commonSituations":"Inconsistent indent after manual editing; mixed tab/space handling making one row appear deeper; copy-paste of a nested example into the middle of a tabular block.","solutions":["Re-indent the offending line to match the other entry rows","Remove unintended extra nesting under the row","Decode with strict:false to skip the over-indented line","Normalize indentation with a formatter before decoding"],"exampleFix":"// before\nusers[2]{name}:\n  name: Ada\n    name: Grace\n\n// after\nusers[2]{name}:\n  name: Ada\n  name: Grace","handlingStrategy":"validation","validationCode":"function tabularRowsUniformDepth(text: string): boolean {\n  const lines = text.split('\\n').filter(l => l.trim())\n  let entryDepth: number | null = null\n  let inTable = false\n  for (const l of lines) {\n    const d = Math.floor(((l.match(/^ */) ?? [''])[0].length) / 2)\n    if (/^ *\\[\\d+\\]\\{.*\\}:/.test(l)) { inTable = true; entryDepth = null; continue }\n    if (inTable) {\n      if (d <= 0) { inTable = false; continue }\n      if (entryDepth === null) entryDepth = d\n      else if (d !== entryDepth) return false\n    }\n  }\n  return true\n}","typeGuard":null,"tryCatchPattern":"try {\n  return decode(input, { strict: true })\n} catch (e) {\n  if (e instanceof ToonDecodeError && e.message.includes('inside keyed tabular object')) {\n    return decode(input, { strict: false })\n  }\n  throw e\n}","preventionTips":["Keep every tabular row at exactly the same indentation depth","Run a formatter after manual edits to tabular blocks","Beware editors converting spaces to tabs inside tables"],"tags":["toon","decoder","strict-mode","indentation","tabular"],"backgroundTag":"indentation-depth-jump","analyzedSha":"604eac266e35166bed6f5b9e3bd586e9c60a9330","analyzedAt":"2026-08-31T11:09:25.043Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}