{"record":{"id":"f112854d76f14b3f","repo":"Freika/dawarich","slug":"failed-to-fetch-points-response-statustext","errorCode":null,"errorMessage":"Failed to fetch points: ${response.statusText}","messagePattern":"Failed to fetch points: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/javascript/maps_maplibre/services/api_client.js","lineNumber":35,"sourceCode":"  async fetchPoints({ start_at, end_at, page = 1, per_page = 1000, signal }) {\n    const params = new URLSearchParams({\n      start_at,\n      end_at,\n      page: page.toString(),\n      per_page: per_page.toString(),\n      slim: \"true\",\n      order: \"asc\",\n    })\n\n    if (this.importId) params.append(\"import_id\", this.importId)\n\n    const response = await fetch(`${this.baseURL}/points?${params}`, {\n      headers: this.getHeaders(),\n      signal,\n    })\n\n    if (!response.ok) {\n      throw new Error(`Failed to fetch points: ${response.statusText}`)\n    }\n\n    const points = await response.json()\n\n    return {\n      points,\n      currentPage: parseInt(response.headers.get(\"X-Current-Page\") || \"1\", 10),\n      totalPages: parseInt(response.headers.get(\"X-Total-Pages\") || \"1\", 10),\n      totalPointsInRange: parseInt(\n        response.headers.get(\"X-Total-Points-In-Range\") || \"0\",\n        10,\n      ),\n      scopedPoints: parseInt(\n        response.headers.get(\"X-Scoped-Points\") || \"0\",\n        10,\n      ),\n    }\n  }","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/javascript/maps_maplibre/services/api_client.js#L17-L53","documentation":"ApiClient.fetchPoints throws when GET /api/v1/points?start_at&end_at&page&per_page&slim=true&order=asc(&import_id) returns a non-OK status. The message embeds response.statusText, which is frequently an empty string over HTTP/2 and h2c proxies, so the real cause (401 bad Bearer key, 422 invalid date range, 500 server error) is invisible in the message. Pagination headers X-Current-Page / X-Total-Pages are only read on success.","triggerScenarios":"Maps V2 initial load or date-range change with an invalid/expired apiKey passed to the ApiClient constructor (401), missing or non-ISO start_at/end_at (422), an import_id scoping to a deleted import, or a 500 from the points endpoint. Also thrown when the user's session ends mid-pagination while fetching subsequent pages.","commonSituations":"Rotated or blank API key in the map element's data attribute, date pickers emitting local-time strings the backend cannot parse, stale import_id kept in page state after re-importing data, server-side timeout on huge date ranges, HTTP/2 deployment stripping statusText so the toast shows just 'Failed to fetch points: '.","solutions":["Log response.status alongside statusText (HTTP/2 makes statusText empty) to identify 401 vs 422 vs 500","Verify the api key passed to new ApiClient(apiKey, importId) is the user's current key","Confirm start_at/end_at are sent and ISO-formatted before the fetch","If importId is set, check the import still exists and belongs to the current user","Wrap fetchPoints callers in try/catch and keep the map in a recoverable 'load failed' state instead of a blank canvas"],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error(`Failed to fetch points: ${response.statusText}`)\n}\n\n// after\nif (!response.ok) {\n  throw new Error(\n    `Failed to fetch points: ${response.status} ${response.statusText}`.trim(),\n  )\n}","handlingStrategy":"try-catch","validationCode":"function assertFetchPointsArgs({ start_at, end_at, apiKey }) {\n  if (!apiKey) throw new Error('API key is required')\n  const s = Date.parse(start_at)\n  const e = Date.parse(end_at)\n  if (Number.isNaN(s) || Number.isNaN(e)) throw new Error('start_at/end_at must be valid dates')\n  if (s > e) throw new Error('start_at must be before end_at')\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await client.fetchPoints({ start_at, end_at, page, signal })\n} catch (error) {\n  console.error(`fetchPoints failed on page ${page}:`, error.message)\n  renderMapErrorState() // keep the map usable instead of a blank canvas\n}","preventionTips":["Log response.status, not statusText - HTTP/2 leaves statusText empty","Pass an AbortController signal so stale paginated requests cancel cleanly on date-range changes","Surface a retry affordance in the UI for transient 5xx instead of silently dropping later pages"],"tags":["fetch","api","pagination","maplibre","points"],"backgroundTag":"http-error-response","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}