{"record":{"id":"cc152f4c012e04db","repo":"mifi/lossless-cut","slug":"segment-start-time-must-precede-end-time","errorCode":null,"errorMessage":"Segment start time must precede end time","messagePattern":"Segment start time must precede end time","errorType":"exception","errorClass":"UserFacingError","httpStatus":null,"severity":"error","filePath":"src/renderer/src/hooks/useSegments.tsx","lineNumber":469,"sourceCode":"  const updateSegAtIndex = useCallback<UpdateSegAtIndex>((index, newProps) => {\n    if (index < 0) return;\n    const cutSegmentsNew = [...cutSegments];\n    const existing = cutSegments[index];\n    invariant(existing != null);\n    cutSegmentsNew.splice(index, 1, { ...existing, ...newProps });\n    safeSetCutSegments(cutSegmentsNew, fileDuration);\n  }, [cutSegments, safeSetCutSegments, fileDuration]);\n\n  const setCutTime = useCallback((type: 'start' | 'end' | 'move', time: number | undefined) => {\n    if (!isDurationValid(fileDuration) || currentCutSeg == null) return;\n\n    const clampStart = (start: number) => Math.min(Math.max(start, 0), fileDuration);\n    const clampEnd = (end?: number | undefined) => (end != null ? Math.min(Math.max(end, 0), fileDuration) : undefined);\n\n    if (type === 'start') {\n      invariant(time != null);\n      if (currentCutSeg.end != null && time >= currentCutSeg.end) {\n        throw new UserFacingError(i18n.t('Segment start time must precede end time'));\n      }\n      updateSegAtIndex(currentSegIndexSafe, { start: clampStart(time) });\n    }\n    if (type === 'end') {\n      if (time != null && time <= currentCutSeg.start) {\n        throw new UserFacingError(i18n.t('Segment start time must precede end time'));\n      }\n      updateSegAtIndex(currentSegIndexSafe, { end: clampEnd(time) });\n    }\n    if (type === 'move') {\n      invariant(time != null);\n      updateSegAtIndex(currentSegIndexSafe, {\n        start: clampStart(time),\n        ...(currentCutSeg.end != null && { end: clampEnd(time + (currentCutSeg.end - currentCutSeg.start)) }),\n      });\n    }\n  }, [currentSegIndexSafe, currentCutSeg, fileDuration, updateSegAtIndex]);\n","sourceCodeStart":451,"sourceCodeEnd":487,"githubUrl":"https://github.com/mifi/lossless-cut/blob/3b9a59c288bf6e11076b583c932cfa48ddab3b02/src/renderer/src/hooks/useSegments.tsx#L451-L487","documentation":"Thrown by setCutTime() in the type==='start' branch when the new start time is not null and is >= the existing segment end. LosslessCut requires every segment to have start < end (a positive-length span), so moving the start to or past the end is rejected. The guard fires before updateSegAtIndex so no invalid state is written.","triggerScenarios":"Calling setCutTime('start', t) with t >= currentCutSeg.end; dragging/typing a start marker that lands on or after the segment's end; programmatically setting start from a slider whose value overshot the end.","commonSituations":"User drags the start handle past the end handle on the timeline; a numeric input/keyboard nudge pushes start beyond end; clamping logic that did not account for the end boundary; importing a segment and nudging its start.","solutions":["Clamp the requested start to be strictly less than end before calling setCutTime('start', ...).","If the user intends to swap, move the end first, then the start.","Validate in the UI: disable the start handle once it reaches the end handle.","Provide an undo when the drag triggers the error."],"exampleFix":"// before\nsetCutTime('start', requestedStart);\n\n// after\nconst safeStart = currentCutSeg.end != null ? Math.min(requestedStart, currentCutSeg.end - epsilon) : requestedStart;\nsetCutTime('start', safeStart);","handlingStrategy":"validation","validationCode":"const epsilon = 0.001;\nif (type === 'start' && currentCutSeg.end != null && requestedStart >= currentCutSeg.end) {\n  // clamp instead of throwing\n  requestedStart = currentCutSeg.end - epsilon;\n}\nsetCutTime('start', requestedStart);","typeGuard":null,"tryCatchPattern":"try {\n  setCutTime('start', requestedStart);\n} catch (err) {\n  if (err instanceof UserFacingError && /start time must precede end/.test(err.message)) {\n    showError('Start cannot be at or after the segment end.');\n    return;\n  }\n  throw err;\n}","preventionTips":["Clamp requested start to < end before calling setCutTime('start', ...).","In the timeline UI, prevent the start handle from crossing the end handle.","If swapping boundaries is intended, move end first then start.","Provide undo for drags that violate the ordering."],"tags":["segments","timeline","validation","bounds","user-input"],"backgroundTag":null,"analyzedSha":"3b9a59c288bf6e11076b583c932cfa48ddab3b02","analyzedAt":"2026-08-12T20:54:25.651Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}