diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx
index 6f9a706..d8fd563 100644
--- a/src/components/video-editor/timeline/Item.tsx
+++ b/src/components/video-editor/timeline/Item.tsx
@@ -79,10 +79,15 @@ export default function Item({
[span.start, span.end],
);
+ // Guarantee a minimum clickable width on the outer wrapper so that
+ // very short items (< 1px) remain visible and interactive.
+ const MIN_ITEM_PX = 16;
+ const safeItemStyle = { ...itemStyle, minWidth: MIN_ITEM_PX };
+
return (
onSelect?.()}
diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx
index 2b03685..9480dc5 100644
--- a/src/components/video-editor/timeline/TimelineEditor.tsx
+++ b/src/components/video-editor/timeline/TimelineEditor.tsx
@@ -116,16 +116,21 @@ function calculateTimelineScale(durationSeconds: number): TimelineScaleConfig {
const intervalMs = Math.round(selectedCandidate.intervalSeconds * 1000);
const gridMs = Math.round(selectedCandidate.gridSeconds * 1000);
- // Set minItemDurationMs to 1ms for maximum granularity
- const minItemDurationMs = 1;
+ // Minimum item duration: at least 500ms or 0.3% of the video, capped at 3s.
+ // Prevents items from being shrunk to invisible sizes.
+ const minItemDurationMs = totalMs > 0
+ ? Math.min(Math.max(500, Math.round(totalMs * 0.003)), 3000)
+ : 500;
const defaultItemDurationMs = Math.min(
Math.max(minItemDurationMs, intervalMs * 2),
totalMs > 0 ? totalMs : intervalMs * 2,
);
+ // Minimum visible range: at least 2s or 0.5% of the video, capped at 15s.
+ // Decoupled from intervalMs so long videos can still zoom in deeply.
const minVisibleRangeMs = totalMs > 0
- ? Math.min(Math.max(intervalMs * 3, minItemDurationMs * 6, 1000), totalMs)
- : Math.max(intervalMs * 3, minItemDurationMs * 6, 1000);
+ ? Math.max(2000, Math.min(Math.round(totalMs * 0.005), 15000))
+ : 2000;
return {
intervalMs,
diff --git a/src/components/video-editor/timeline/TimelineWrapper.tsx b/src/components/video-editor/timeline/TimelineWrapper.tsx
index 35685b3..37ebe94 100644
--- a/src/components/video-editor/timeline/TimelineWrapper.tsx
+++ b/src/components/video-editor/timeline/TimelineWrapper.tsx
@@ -135,7 +135,10 @@ export default function TimelineWrapper({
const activeItemId = event.active.id as string;
let clampedSpan = clampSpanToBounds(updatedSpan);
- if (clampedSpan.end - clampedSpan.start < Math.min(minItemDurationMs, totalMs || minItemDurationMs)) {
+ const effectiveMinDuration = totalMs > 0
+ ? Math.min(minItemDurationMs, totalMs)
+ : minItemDurationMs;
+ if (clampedSpan.end - clampedSpan.start < effectiveMinDuration) {
return;
}