mifi/lossless-cut · error · UserFacingError

Segment start time must precede end time

Error message

Segment start time must precede end time

What it means

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.

Source

Thrown at src/renderer/src/hooks/useSegments.tsx:469

  const updateSegAtIndex = useCallback<UpdateSegAtIndex>((index, newProps) => {
    if (index < 0) return;
    const cutSegmentsNew = [...cutSegments];
    const existing = cutSegments[index];
    invariant(existing != null);
    cutSegmentsNew.splice(index, 1, { ...existing, ...newProps });
    safeSetCutSegments(cutSegmentsNew, fileDuration);
  }, [cutSegments, safeSetCutSegments, fileDuration]);

  const setCutTime = useCallback((type: 'start' | 'end' | 'move', time: number | undefined) => {
    if (!isDurationValid(fileDuration) || currentCutSeg == null) return;

    const clampStart = (start: number) => Math.min(Math.max(start, 0), fileDuration);
    const clampEnd = (end?: number | undefined) => (end != null ? Math.min(Math.max(end, 0), fileDuration) : undefined);

    if (type === 'start') {
      invariant(time != null);
      if (currentCutSeg.end != null && time >= currentCutSeg.end) {
        throw new UserFacingError(i18n.t('Segment start time must precede end time'));
      }
      updateSegAtIndex(currentSegIndexSafe, { start: clampStart(time) });
    }
    if (type === 'end') {
      if (time != null && time <= currentCutSeg.start) {
        throw new UserFacingError(i18n.t('Segment start time must precede end time'));
      }
      updateSegAtIndex(currentSegIndexSafe, { end: clampEnd(time) });
    }
    if (type === 'move') {
      invariant(time != null);
      updateSegAtIndex(currentSegIndexSafe, {
        start: clampStart(time),
        ...(currentCutSeg.end != null && { end: clampEnd(time + (currentCutSeg.end - currentCutSeg.start)) }),
      });
    }
  }, [currentSegIndexSafe, currentCutSeg, fileDuration, updateSegAtIndex]);

View on GitHub (pinned to 3b9a59c288)

Solutions

  1. Clamp the requested start to be strictly less than end before calling setCutTime('start', ...).
  2. If the user intends to swap, move the end first, then the start.
  3. Validate in the UI: disable the start handle once it reaches the end handle.
  4. Provide an undo when the drag triggers the error.

Example fix

// before
setCutTime('start', requestedStart);

// after
const safeStart = currentCutSeg.end != null ? Math.min(requestedStart, currentCutSeg.end - epsilon) : requestedStart;
setCutTime('start', safeStart);
Defensive patterns

Strategy: validation

Validate before calling

const epsilon = 0.001;
if (type === 'start' && currentCutSeg.end != null && requestedStart >= currentCutSeg.end) {
  // clamp instead of throwing
  requestedStart = currentCutSeg.end - epsilon;
}
setCutTime('start', requestedStart);

Try / catch

try {
  setCutTime('start', requestedStart);
} catch (err) {
  if (err instanceof UserFacingError && /start time must precede end/.test(err.message)) {
    showError('Start cannot be at or after the segment end.');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mifi/lossless-cut@3b9a59c288 (2026-08-12). Data as JSON: /api/errors/cc152f4c012e04db. Report an issue: GitHub.