feat: separate Backlinks and History with horizontal dividers in Inspector

Add visual separation between properties, Backlinks, and History sections
using Separator components. Restyle Backlinks with arrow-up-right icon header
and blue clickable links. Restyle History with counter-clockwise icon header,
combined hash+message blue links, and muted date below. Both sections now
hide entirely when empty instead of showing placeholder text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-29 16:04:28 +02:00
parent 9df41c3287
commit 730b56ecbd
5 changed files with 71 additions and 123 deletions

View File

@@ -1,7 +1,5 @@
import { useState } from 'react'
import type { VaultEntry } from '../../types'
import { CaretRight, Trash } from '@phosphor-icons/react'
import { getTypeColor } from '../../utils/typeColors'
import { ArrowUpRight, Trash } from '@phosphor-icons/react'
import { isEmoji } from '../../utils/emoji'
import { entryStatusTitle } from './shared'
import { StatusSuffix } from './LinkButton'
@@ -11,23 +9,21 @@ export interface BacklinkItem {
context: string | null
}
function BacklinkEntry({ entry, context, typeEntryMap, onNavigate }: {
function BacklinkEntry({ entry, context, onNavigate }: {
entry: VaultEntry
context: string | null
typeEntryMap: Record<string, VaultEntry>
onNavigate: (target: string) => void
}) {
const te = typeEntryMap[entry.isA ?? '']
const isDimmed = entry.archived || entry.trashed
return (
<button
className="flex w-full cursor-pointer flex-col items-start gap-0.5 border-none bg-transparent p-0 text-left hover:opacity-80"
className="flex w-full cursor-pointer flex-col items-start gap-0.5 border-none bg-transparent p-0 text-left hover:underline"
onClick={() => onNavigate(entry.title)}
title={entryStatusTitle(entry)}
>
<span
className="flex items-center gap-1 text-xs font-medium"
style={{ color: isDimmed ? 'var(--muted-foreground)' : getTypeColor(entry.isA, te?.color) }}
className="flex items-center gap-1 text-xs text-primary"
style={isDimmed ? { color: 'var(--muted-foreground)' } : undefined}
>
{entry.trashed && <Trash size={12} className="shrink-0" />}
{entry.icon && isEmoji(entry.icon) && <span className="shrink-0">{entry.icon}</span>}
@@ -43,42 +39,28 @@ function BacklinkEntry({ entry, context, typeEntryMap, onNavigate }: {
)
}
export function BacklinksPanel({ backlinks, typeEntryMap, onNavigate }: {
export function BacklinksPanel({ backlinks, onNavigate }: {
backlinks: BacklinkItem[]
typeEntryMap: Record<string, VaultEntry>
onNavigate: (target: string) => void
}) {
const [expanded, setExpanded] = useState(false)
if (backlinks.length === 0) return null
return (
<div>
<button
className="font-mono-overline mb-2 flex w-full cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left text-muted-foreground hover:text-foreground"
onClick={() => setExpanded((v) => !v)}
data-testid="backlinks-toggle"
>
<CaretRight
size={12}
className="shrink-0 transition-transform"
style={{ transform: expanded ? 'rotate(90deg)' : undefined }}
/>
Backlinks ({backlinks.length})
</button>
{expanded && (
<div className="flex flex-col gap-1.5" data-testid="backlinks-list">
{backlinks.map(({ entry, context }) => (
<BacklinkEntry
key={entry.path}
entry={entry}
context={context}
typeEntryMap={typeEntryMap}
onNavigate={onNavigate}
/>
))}
</div>
)}
<h4 className="font-mono-overline mb-2 flex items-center gap-1 text-muted-foreground">
<ArrowUpRight size={12} className="shrink-0" />
Backlinks
</h4>
<div className="flex flex-col gap-1.5" data-testid="backlinks-list">
{backlinks.map(({ entry, context }) => (
<BacklinkEntry
key={entry.path}
entry={entry}
context={context}
onNavigate={onNavigate}
/>
))}
</div>
</div>
)
}

View File

@@ -1,3 +1,4 @@
import { ArrowCounterClockwise } from '@phosphor-icons/react'
import type { GitCommit } from '../../types'
function formatRelativeDate(timestamp: number): string {
@@ -11,26 +12,32 @@ function formatRelativeDate(timestamp: number): string {
}
export function GitHistoryPanel({ commits, onViewCommitDiff }: { commits: GitCommit[]; onViewCommitDiff?: (commitHash: string) => void }) {
if (commits.length === 0) return null
return (
<div>
<h4 className="font-mono-overline mb-2 text-muted-foreground">History</h4>
{commits.length === 0
? <p className="m-0 text-[13px] text-muted-foreground">No revision history</p>
: (
<div className="flex flex-col gap-2.5">
{commits.map((c) => (
<div key={c.hash} style={{ borderLeft: '2px solid var(--border)', paddingLeft: 10 }}>
<div className="mb-0.5 flex items-center justify-between">
<button className="border-none bg-transparent p-0 font-mono text-primary cursor-pointer hover:underline" style={{ fontSize: 11 }} onClick={() => onViewCommitDiff?.(c.hash)} title={`View diff for ${c.shortHash}`}>{c.shortHash}</button>
<span className="text-muted-foreground" style={{ fontSize: 10 }}>{formatRelativeDate(c.date)}</span>
</div>
<div className="truncate text-xs text-secondary-foreground">{c.message}</div>
{c.author && <div className="truncate text-muted-foreground" style={{ fontSize: 10, marginTop: 1 }}>{c.author}</div>}
</div>
))}
<h4 className="font-mono-overline mb-2 flex items-center gap-1 text-muted-foreground">
<ArrowCounterClockwise size={12} className="shrink-0" />
History
</h4>
<div className="flex flex-col gap-2.5">
{commits.map((c) => (
<div key={c.hash} style={{ borderLeft: '2px solid var(--border)', paddingLeft: 10 }}>
<button
className="mb-0.5 w-full cursor-pointer truncate border-none bg-transparent p-0 text-left text-xs text-primary hover:underline"
onClick={() => onViewCommitDiff?.(c.hash)}
title={`View diff for ${c.shortHash}`}
>
<span className="font-mono" style={{ fontSize: 11 }}>{c.shortHash}</span>
{' · '}
{c.message}
</button>
<div className="text-muted-foreground" style={{ fontSize: 10 }}>
{formatRelativeDate(c.date)}
</div>
</div>
)
}
))}
</div>
</div>
)
}