Make family branches readable and fix Library part previews.

Lead rows with Start/pN labels and fork headers, collapse hidden frame strips to one glow, and stop Library part thumbs from leaking or cloning the Start frame.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 20:32:40 -05:00
co-authored by Cursor
parent 8f80cbd39b
commit 47f14d7be2
4 changed files with 67 additions and 26 deletions
+33 -8
View File
@@ -303,11 +303,11 @@
</svg>
</button>
</div>
<div v-if="focused.kind === 'clip' && focusedDisplayNodes.length > 1" class="mt-3 space-y-1.5">
<div v-if="focused.kind === 'clip' && focusedDisplayNodes.length > 1 && !detailsHidden" class="mt-3 space-y-1.5">
<template v-for="node in focusedDisplayNodes" :key="node.key">
<p
v-if="node.type === 'fork'"
class="px-1 text-[11px] font-medium uppercase tracking-wide text-amber-200/70"
class="rounded-md border-l-2 border-amber-300/50 bg-amber-400/5 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-amber-100"
:style="node.depth ? { marginLeft: `${Math.min(node.depth, 6) * 0.65}rem` } : undefined"
>
{{ node.label }}
@@ -320,28 +320,48 @@
:style="node.depth ? { marginLeft: `${Math.min(node.depth, 6) * 0.65}rem` } : undefined"
@click="focusPart(node.clip.id)"
>
<img :src="partThumb(node.clip)" alt="" class="h-12 w-12 shrink-0 rounded object-cover" loading="lazy">
<span class="relative h-12 w-12 shrink-0 overflow-hidden rounded bg-zinc-900">
<span v-if="node.clip.hideThumbnail" class="sensitive-glow block h-full w-full" />
<img
v-else
:src="partThumb(node.clip)"
alt=""
class="h-full w-full object-cover"
loading="lazy"
>
</span>
<span class="min-w-0 flex-1">
<span class="block truncate text-xs font-medium text-zinc-100">{{ node.clip.name }}</span>
<span class="block truncate text-xs font-medium text-amber-100">{{ clipFamilyPartTitle(node.clip, focused.group!.parts) }}</span>
<span v-if="node.fromLabel || node.branchLabel" class="block truncate text-[11px] text-amber-200/80">
{{ [node.fromLabel, node.branchLabel].filter(Boolean).join(' · ') }}
</span>
<span class="block truncate text-[11px] text-zinc-500">{{ node.clip.name }}</span>
</span>
</button>
</template>
</div>
<div v-else-if="focusedParts.length > 1" class="mt-3 grid grid-cols-5 gap-1.5">
<div v-else-if="focusedParts.length > 1 && !detailsHidden" class="mt-3 grid grid-cols-5 gap-1.5">
<button
v-for="part in focusedParts"
:key="part.id"
type="button"
class="overflow-hidden rounded-lg border border-white/10"
class="relative overflow-hidden rounded-lg border border-white/10"
:class="part.id === focusedPartId ? 'ring-2 ring-amber-300' : ''"
@click="focusPart(part.id)"
>
<img :src="partThumb(part)" alt="" class="aspect-square w-full object-cover" loading="lazy">
<span v-if="'hideThumbnail' in part && part.hideThumbnail" class="sensitive-glow block aspect-square w-full" />
<img
v-else
:src="partThumb(part)"
alt=""
class="aspect-square w-full object-cover"
loading="lazy"
>
</button>
</div>
<p v-else-if="focused.kind === 'clip' && focusedDisplayNodes.length > 1 && detailsHidden" class="mt-3 text-xs text-zinc-500">
{{ focusedDisplayNodes.filter(node => node.type === 'clip').length }} parts hidden with preview
</p>
<div class="mt-3 flex flex-wrap gap-2">
<button type="button" class="rounded-full border border-amber-300/40 px-3 py-1.5 text-xs text-amber-100" @click="openInStudio(focused)">{{ focused.kind === 'track' ? 'Open in Music' : 'Open in Studio' }}</button>
<button v-if="focused.kind !== 'track'" type="button" class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-300 hover:text-white" @click="useInStudio(focused)">Use as input</button>
@@ -481,6 +501,7 @@ import {
chronologicalStills,
clipBranchFromLabel,
clipBranchLetterLabel,
clipFamilyPartTitle,
familyBranchSummary,
familyDisplayNodes,
formatClipDuration,
@@ -852,7 +873,11 @@ function cardMeta(entry: LibraryEntry) {
}
function partThumb(part: LibraryClip | LibraryStill) {
if ('hideThumbnail' in part) return `/api/library/clips/${part.id}/thumb`
if ('hideThumbnail' in part && !('role' in part)) {
const clip = part as LibraryClip
// Prefer this clip’s first frame so siblings don’t all look like the Start thumb.
return `/api/library/clips/${clip.id}/first-frame?thumb=1&v=${encodeURIComponent(clip.id)}`
}
return `/api/library/stills/${part.id}?thumb=1`
}