{"record":{"id":"eb9cd65115abc158","repo":"datawhalechina/hello-agents","slug":"error-eb9cd6","errorCode":null,"errorMessage":"保存失败: ","messagePattern":"保存失败: ","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/lgs-only-NovelGenerator/frontend/index.html","lineNumber":411,"sourceCode":"                    }\n                };\n\n                const updateOutline = async () => {\n                    if (!outline.id) return;\n                    loading.value = true;\n                    currentAction.value = 'update_outline';\n                    try {\n                        const res = await fetch(`${API_URL}/outline/update`, {\n                            method: 'PUT',\n                            headers: {'Content-Type': 'application/json'},\n                            body: JSON.stringify({\n                                novel_id: project.novel_id,\n                                title: project.title,\n                                note_id: outline.id,\n                                content: outline.content\n                            })\n                        });\n                        if (!res.ok) throw new Error(await res.text());\n                        alert('大纲保存成功！');\n                    } catch (e) {\n                        alert('保存失败: ' + e);\n                    } finally {\n                        loading.value = false;\n                        currentAction.value = null;\n                    }\n                };\n\n                const deleteOutline = async () => {\n                    if(!confirm('确定要删除当前大纲吗？此操作不可恢复。')) return;\n                    loading.value = true;\n                    currentAction.value = 'delete_outline';\n                    try {\n                        const params = new URLSearchParams({\n                            novel_id: project.novel_id,\n                            title: project.title,\n                            note_id: outline.id","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/lgs-only-NovelGenerator/frontend/index.html#L393-L429","documentation":"'保存失败: ' + e (save failed) is alerted by the NovelGenerator frontend when PUT {API_URL}/outline/update responds non-OK; the thrown Error wraps the raw response text. The request persists the edited outline {novel_id, title, note_id, content} — so the most common failure is the backend rejecting an outline id that no longer exists (outline.id was set from a previous generate and the backend store changed).","triggerScenarios":"PUT /outline/update returns 404 (note_id/novel_id unknown — backend restarted, outline deleted elsewhere, outline.id never set because generate failed earlier), 422 (missing required fields such as empty title), 500 (storage/write failure). Clicking save before any outline was generated leaves outline.id null → guaranteed rejection.","commonSituations":"Saving with no outline loaded (id null); backend memory store reset between generate and save; concurrent edits deleting the note; required field omitted in the edit.","solutions":["Disable the save button unless outline.id is set (generate must succeed first).","Read the alert text — the server body distinguishes 404 'not found' from validation errors.","If the backend lost state (restart), regenerate the outline to obtain a fresh note_id.","Guard title/content non-empty before the PUT."],"exampleFix":"// before\nconst updateOutline = async () => { /* no guard */ }\n\n// after\nconst updateOutline = async () => {\n  if (!outline.id) { alert('请先生成大纲'); return; }\n  if (!outline.content?.trim()) { alert('大纲内容为空'); return; }\n  // ... existing fetch\n}","handlingStrategy":"validation","validationCode":"if (!outline.id) { alert('请先生成大纲再保存'); return; }\nif (!outline.content?.trim()) { alert('大纲内容不能为空'); return; }\nif (!project.novel_id || !project.title) { alert('项目信息不完整，请重新加载'); return; }","typeGuard":"function isOutlineUpdatePayload(p: unknown): p is { novel_id: string; title: string; note_id: string; content: string } {\n  return typeof p === 'object' && p !== null\n    && typeof (p as { note_id?: unknown }).note_id === 'string'\n    && typeof (p as { content?: unknown }).content === 'string';\n}","tryCatchPattern":"try {\n  const res = await fetch(`${API_URL}/outline/update`, opts);\n  if (!res.ok) throw new Error(`${res.status}: ${(await res.text()).slice(0, 200)}`);\n  alert('大纲保存成功！');\n} catch (e) {\n  alert('保存失败: ' + (e instanceof Error ? e.message : String(e)));\n} finally {\n  loading.value = false;\n}","preventionTips":["Disable save until outline.id exists — null note_id guarantees a server rejection.","If the backend stores notes in memory, expect ids to die on restart; regenerate after restarts.","Always send non-empty title/content; the backend rejects blanks."],"tags":["http","fetch","crud","stale-id","novel-generation"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}