Show family branches in plain English with Start as the card head.

Collapsed cards use parts/forks/longest instead of pN codes; expanded Studio and Library trees get fork headers and from/branch labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 20:11:28 -05:00
co-authored by Cursor
parent e790c9bff7
commit 8f80cbd39b
3 changed files with 158 additions and 59 deletions
+41 -5
View File
@@ -303,7 +303,34 @@
</svg>
</button>
</div>
<div v-if="focusedParts.length > 1" class="mt-3 grid grid-cols-5 gap-1.5">
<div v-if="focused.kind === 'clip' && focusedDisplayNodes.length > 1" 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"
:style="node.depth ? { marginLeft: `${Math.min(node.depth, 6) * 0.65}rem` } : undefined"
>
{{ node.label }}
</p>
<button
v-else
type="button"
class="flex w-full items-center gap-2 overflow-hidden rounded-lg border border-white/10 p-1 text-left"
:class="node.clip.id === focusedPartId ? 'ring-2 ring-amber-300' : ''"
: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="min-w-0 flex-1">
<span class="block truncate text-xs font-medium text-zinc-100">{{ node.clip.name }}</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>
</button>
</template>
</div>
<div v-else-if="focusedParts.length > 1" class="mt-3 grid grid-cols-5 gap-1.5">
<button
v-for="part in focusedParts"
:key="part.id"
@@ -453,10 +480,13 @@ import {
buildLibraryEntries,
chronologicalStills,
clipBranchFromLabel,
familyBranchHint,
clipBranchLetterLabel,
familyBranchSummary,
familyDisplayNodes,
formatClipDuration,
formatWhen,
treeOrderedParts,
type FamilyDisplayNode,
type LibraryClip,
type LibraryEntry,
type LibraryFilter,
@@ -614,6 +644,10 @@ const focusedParts = computed(() => {
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
return focused.value?.stillGroup?.parts || []
})
const focusedDisplayNodes = computed((): FamilyDisplayNode[] => {
if (focused.value?.kind !== 'clip' || !focused.value.group) return []
return familyDisplayNodes(focused.value.group.parts)
})
const focusedNavParts = computed(() => {
const parts = focusedParts.value
if (parts.length < 2) return []
@@ -624,7 +658,9 @@ const focusedBranchHint = computed(() => {
if (focused.value?.kind !== 'clip' || !focused.value.group) return ''
const parts = focused.value.group.parts
const current = parts.find(part => part.id === focusedPartId.value)
return (current && clipBranchFromLabel(current, parts)) || familyBranchHint(parts)
if (!current) return familyBranchSummary(parts)
const bits = [clipBranchFromLabel(current, parts), clipBranchLetterLabel(current)].filter(Boolean)
return bits.join(' · ') || familyBranchSummary(parts)
})
const focusedNavIndex = computed(() => {
const id = focusedPartId.value
@@ -806,8 +842,8 @@ function partCount(entry: LibraryEntry) {
function cardMeta(entry: LibraryEntry) {
if (entry.kind === 'clip' && entry.group) {
const branches = familyBranchHint(entry.group.parts)
return [formatClipDuration(entry.group.head.duration), formatWhen(entry.createdAt), branches].filter(Boolean).join(' · ')
const summary = familyBranchSummary(entry.group.parts)
return [formatClipDuration(entry.group.head.duration), formatWhen(entry.createdAt), summary].filter(Boolean).join(' · ')
}
if (entry.kind === 'track' && entry.track) {
return [formatClipDuration(entry.track.duration), formatWhen(entry.createdAt)].filter(Boolean).join(' · ')