{"record":{"id":"9eef84d22ea4bab7","repo":"BloopAI/vibe-kanban","slug":"fallback-response-missing-table-array","errorCode":null,"errorMessage":"Fallback response missing \"${table}\" array","messagePattern":"Fallback response missing \"(.+?)\" array","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/electric/collections.ts","lineNumber":417,"sourceCode":"      metadata: {},\n    });\n  }\n\n  syncParams.commit();\n  syncParams.markReady();\n}\n\nfunction extractFallbackRows(\n  payload: unknown,\n  table: string\n): Array<ElectricRow> {\n  if (!payload || typeof payload !== 'object') {\n    throw new Error(`Fallback response for \"${table}\" is not an object`);\n  }\n\n  const rows = (payload as Record<string, unknown>)[table];\n  if (!Array.isArray(rows)) {\n    throw new Error(`Fallback response missing \"${table}\" array`);\n  }\n\n  return rows as Array<ElectricRow>;\n}\n\nasync function parseResponseError(\n  response: Response,\n  fallbackMessage: string\n): Promise<string> {\n  try {\n    const body = (await response.json()) as {\n      message?: string;\n      error?: string;\n    };\n    return body.message || body.error || fallbackMessage;\n  } catch {\n    return fallbackMessage;\n  }","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/electric/collections.ts#L399-L435","documentation":"extractFallbackRows throws this when the fallback payload is an object but does not contain an array under the payload[table] key. The library expects each fallback response to embed the row list under the exact table name; a missing, null, or non-array value means the sync snapshot cannot be built.","triggerScenarios":"Server response uses a different key ('data', 'items', 'rows') instead of the table name; the table key is present but null; pagination envelope like { tasks: { rows: [...] } }; empty response object {} from a handler that forgot to include rows.","commonSituations":"API contract drift after backend refactor; table renamed in Electric shape but not in the REST endpoint (or vice versa); endpoint returning { tasks: null } for 'no data'; developer implementing a new fallback route against the wrong response contract.","solutions":["Return the rows directly under the table-name key as a JSON array: { \"<table>\": [...] }.","Align the ShapeDefinition.table string with the server's response key.","Guard the server handler against returning null for empty datasets (use []).","Add a response contract test for the fallback endpoint."],"exampleFix":"// before (server)\nres.json({ data: rows });\n// after (server)\nres.json({ tasks: rows ?? [] }); // key must match shape.table, value must be an array","handlingStrategy":"validation","validationCode":"const payload = await response.json();\nif (!Array.isArray(payload?.[table])) {\n  throw new Error(`Fallback response must contain a \"${table}\" array`);\n}","typeGuard":"function hasTableRows(payload: unknown, table: string): payload is { [k: string]: unknown } & Record<typeof table, ElectricRow[]> {\n  return typeof payload === 'object' && payload !== null &&\n    Array.isArray((payload as Record<string, unknown>)[table]);\n}","tryCatchPattern":"try {\n  const rows = extractFallbackRows(payload, table);\n} catch (e) {\n  console.error('Fallback contract violation:', e.message, 'payload keys:', Object.keys(payload ?? {}));\n}","preventionTips":["Return rows under the exact table-name key as an array","Use [] instead of null for empty datasets","Rename table + endpoint key together when refactoring","Add a shared zod schema for the fallback envelope"],"tags":["validation","json","electric-sql","fallback-sync"],"backgroundTag":"unexpected-response-shape","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}