{"record":{"id":"3c8611d4c42b0438","repo":"jamiepine/voicebox","slug":"story-item-not-found-or-invalid-trim-values","errorCode":null,"errorMessage":"Story item not found or invalid trim values","messagePattern":"Story item not found or invalid trim values","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/routes/stories.py","lineNumber":150,"sourceCode":"):\n    \"\"\"Move a story item (update position and/or track).\"\"\"\n    item = await stories.move_story_item(story_id, item_id, data, db)\n    if item is None:\n        raise HTTPException(status_code=404, detail=\"Story item not found\")\n    return item\n\n\n@router.put(\"/stories/{story_id}/items/{item_id}/trim\", response_model=models.StoryItemDetail)\nasync def trim_story_item(\n    story_id: str,\n    item_id: str,\n    data: models.StoryItemTrim,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Trim a story item.\"\"\"\n    item = await stories.trim_story_item(story_id, item_id, data, db)\n    if item is None:\n        raise HTTPException(status_code=404, detail=\"Story item not found or invalid trim values\")\n    return item\n\n\n@router.put(\"/stories/{story_id}/items/{item_id}/volume\", response_model=models.StoryItemDetail)\nasync def update_story_item_volume(\n    story_id: str,\n    item_id: str,\n    data: models.StoryItemVolumeUpdate,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Set a story item's per-clip volume (linear gain, 0.0–2.0).\"\"\"\n    item = await stories.update_story_item_volume(story_id, item_id, data, db)\n    if item is None:\n        raise HTTPException(status_code=404, detail=\"Story item not found\")\n    return item\n\n\n@router.post(\"/stories/{story_id}/items/{item_id}/split\", response_model=list[models.StoryItemDetail])","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/stories.py#L132-L168","documentation":"Returned by PUT /stories/{story_id}/items/{item_id}/trim when stories.trim_story_item returns None (HTTP 404). The None sentinel is overloaded: it covers both 'item not found' and 'invalid trim values' (e.g., trim points out of range or start >= end), because the service returns the same falsy signal for either condition.","triggerScenarios":"Trimming a non-existent item, or supplying trim start/end values that are negative, exceed clip duration, or where start >= end. After a prior trim, the item's effective bounds may have changed, making the new values invalid.","commonSituations":"Editor computes trim points against stale clip duration. item_id stale after split. Trim UI allowing start >= end or out-of-bounds markers.","solutions":["Pre-validate trim values: 0 <= start < end <= item.duration using the current item detail.","Confirm item_id exists in the story before trimming.","Refresh item detail after prior trims to get updated bounds.","Clamp trim markers to [0, duration] and ensure start < end client-side."],"exampleFix":"// before\nawait api.put(`/stories/${sid}/items/${iid}/trim`, { start, end });\n\n// after\nconst item = await getItem(sid, iid);\nif (!item) throw new NotFound();\nconst s = clamp(start, 0, item.duration);\nconst e = clamp(end, 0, item.duration);\nif (s >= e) throw new UserError('start must be < end');\nawait api.put(`/stories/${sid}/items/${iid}/trim`, { start: s, end: e });","handlingStrategy":"validation","validationCode":"function validateTrim(item, start, end) {\n  if (!item) throw new Error('item not found');\n  const s = Math.max(0, Math.min(start, item.duration));\n  const e = Math.max(0, Math.min(end, item.duration));\n  if (s >= e) throw new Error('start must be < end');\n  return { start: s, end: e };\n}","typeGuard":"function isTrimPayload(v) { return !!v && typeof v.start === 'number' && typeof v.end === 'number'; }","tryCatchPattern":"try { await api.put(`/stories/${sid}/items/${iid}/trim`, payload); }\ncatch (e) { if (e.response?.status === 404) { await refreshItem(sid, iid); showUser('item missing or trim invalid'); } throw e; }","preventionTips":["Pre-validate 0 <= start < end <= item.duration using current item detail.","Refresh item detail after prior trims to get updated bounds.","Clamp markers client-side and prevent start >= end."],"tags":["fastapi","http","not-found","stories","items","trim","validation","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}