{"record":{"id":"1657fddec9616458","repo":"odysseus-dev/odysseus","slug":"http-r-status-1657fd","errorCode":null,"errorMessage":"HTTP ${r.status}","messagePattern":"HTTP \\$\\{r\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/notes.js","lineNumber":479,"sourceCode":"}\n\nasync function _saveNote(note) {\n  const method = note.id ? 'PUT' : 'POST';\n  const url = note.id ? `${API_BASE}/api/notes/${note.id}` : `${API_BASE}/api/notes`;\n  const res = await fetch(url, {\n    method, credentials: 'same-origin',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(note),\n  });\n  if (!res.ok) throw new Error('Failed to save note');\n  return await res.json();\n}\n\nasync function _deleteNoteApi(id) {\n  // v2 review — used to swallow 4xx/5xx silently. Throw so callers can\n  // distinguish success vs failure and toast accordingly.\n  const r = await fetch(`${API_BASE}/api/notes/${id}`, { method: 'DELETE', credentials: 'same-origin' });\n  if (!r.ok) throw new Error('HTTP ' + r.status);\n}\n\nasync function _patchNote(id, patch) {\n  const res = await fetch(`${API_BASE}/api/notes/${id}`, {\n    method: 'PUT', credentials: 'same-origin',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(patch),\n  });\n  if (!res.ok) throw new Error('Failed to update note');\n  return await res.json();\n}\n\n// ---- Helpers ----\n\nfunction _esc(s) { return uiModule.esc ? uiModule.esc(s || '') : (s || '').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction _attrEsc(s) {\n  return String(s || '')\n    .replace(/\"/g, '&quot;')","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/notes.js#L461-L497","documentation":"Thrown by _deleteNoteApi in notes.js when DELETE /api/notes/{id} returns non-ok. Per the inline comment this was deliberate: the function used to swallow 4xx/5xx, and now throws 'HTTP <status>' so callers can distinguish success from failure and toast accordingly.","triggerScenarios":"Deleting a note that was already deleted server-side (404); notes store file locked or unwritable (500); expired credentials (401).","commonSituations":"Double-clicking delete or having the same notes open in two tabs; server data reset while the UI list was cached.","solutions":["Treat 404 as success (note already gone) in the caller, then refresh the list.","For 500, check write permissions on the notes storage backend.","Reload the notes module to resync state and retry."],"exampleFix":"// before\nif (!r.ok) throw new Error('HTTP ' + r.status);\n\n// after (idempotent delete)\nif (!r.ok && r.status !== 404) throw new Error('HTTP ' + r.status);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await _deleteNoteApi(id); } catch (e) { if (e.message.includes('404')) { /* already gone — treat as success */ } else { toast('Delete failed: ' + e.message); } } finally { refreshList(); }","preventionTips":["Treat delete-404 as idempotent success","Always refresh the list after delete attempts regardless of outcome","Keep throwing (per the v2 review comment) so callers decide policy"],"tags":["notes","http","delete","crud"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}