Skip to content

Commit a537c57

Browse files
committed
UI cleanup
1 parent 55247bf commit a537c57

File tree

12 files changed

+551
-178
lines changed

12 files changed

+551
-178
lines changed

app/components/canvas/CanvasRenderer.tsx

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* - Canvas config determines layout
1212
*/
1313

14-
import { useCallback, type ReactNode } from 'react'
14+
import React, { useCallback, useState, type ReactNode } from 'react'
1515
import type { Column } from '../../types/app-types'
1616
import type {
1717
PullRequest,
@@ -167,6 +167,7 @@ export function CanvasRenderer({
167167
activeCanvas,
168168
resizeColumn,
169169
reorderColumns,
170+
setColumnPanel,
170171
} = useCanvas()
171172

172173
// Render a list panel based on column config
@@ -293,6 +294,8 @@ export function CanvasRenderer({
293294
onDoubleClickStash={handlers.onDoubleClickStash}
294295
onSelectRepo={handlers.onSelectRepo}
295296
onDoubleClickRepo={handlers.onDoubleClickRepo}
297+
onCreateBranch={handlers.onCreateBranch}
298+
onCreateWorktree={handlers.onCreateWorktree}
296299
formatRelativeTime={handlers.formatRelativeTime}
297300
/>
298301
)
@@ -322,18 +325,75 @@ export function CanvasRenderer({
322325
// Render a viz panel based on column config
323326
const renderVizSlot = useCallback(
324327
(column: Column): ReactNode => {
328+
// Shared viz header with chart selector
329+
const VizHeader = ({
330+
panel,
331+
label,
332+
icon
333+
}: {
334+
panel: string
335+
label: string
336+
icon: string
337+
}) => {
338+
const [controlsOpen, setControlsOpen] = useState(false)
339+
340+
const chartOptions = [
341+
{ id: 'git-graph', label: 'Git Graph', icon: '◉' },
342+
{ id: 'timeline', label: 'Timeline', icon: '◔' },
343+
{ id: 'tech-tree', label: 'Tech Tree', icon: '⬡' },
344+
]
345+
346+
return (
347+
<>
348+
<div
349+
className={`column-header clickable-header ${controlsOpen ? 'open' : ''}`}
350+
onClick={() => setControlsOpen(!controlsOpen)}
351+
>
352+
<div className="column-title">
353+
<h2>
354+
<span className="column-icon">{icon}</span>
355+
{label}
356+
</h2>
357+
<span className={`header-chevron ${controlsOpen ? 'open' : ''}`}></span>
358+
</div>
359+
</div>
360+
{controlsOpen && (
361+
<div className="column-controls" onClick={(e) => e.stopPropagation()}>
362+
<div className="control-row">
363+
<label>Chart</label>
364+
<select
365+
value={panel}
366+
onChange={(e) => {
367+
// Update column panel through canvas context
368+
if (activeCanvas) {
369+
setColumnPanel(activeCanvas.id, column.id, e.target.value as import('../../types/app-types').PanelType)
370+
}
371+
setControlsOpen(false)
372+
}}
373+
className="control-select"
374+
>
375+
{chartOptions.map((opt) => (
376+
<option key={opt.id} value={opt.id}>
377+
{opt.icon} {opt.label}
378+
</option>
379+
))}
380+
</select>
381+
</div>
382+
</div>
383+
)}
384+
</>
385+
)
386+
}
387+
325388
switch (column.panel) {
326389
case 'git-graph':
327390
return (
328391
<div className="viz-panel git-graph-panel">
329-
<div className="column-header">
330-
<div className="column-title">
331-
<h2>
332-
<span className="column-icon">{column.icon || '◉'}</span>
333-
{column.label || 'History'}
334-
</h2>
335-
</div>
336-
</div>
392+
<VizHeader
393+
panel={column.panel}
394+
label={column.label || 'History'}
395+
icon={column.icon || '◉'}
396+
/>
337397
<div className="viz-panel-content">
338398
<GitGraph
339399
commits={data.commits}
@@ -349,27 +409,31 @@ export function CanvasRenderer({
349409
case 'timeline':
350410
return (
351411
<div className="viz-panel timeline-panel">
352-
<ContributorChart
353-
topN={10}
354-
bucketSize="week"
355-
height={500}
356-
invertedTheme={true}
357-
onManageUsers={handlers.onOpenMailmap}
412+
<VizHeader
413+
panel={column.panel}
414+
label={column.label || 'Timeline'}
415+
icon={column.icon || '◔'}
358416
/>
417+
<div className="viz-panel-content">
418+
<ContributorChart
419+
topN={10}
420+
bucketSize="week"
421+
height={500}
422+
invertedTheme={true}
423+
onManageUsers={handlers.onOpenMailmap}
424+
/>
425+
</div>
359426
</div>
360427
)
361428

362429
case 'tech-tree':
363430
return (
364431
<div className="viz-panel tech-tree-panel">
365-
<div className="column-header">
366-
<div className="column-title">
367-
<h2>
368-
<span className="column-icon">{column.icon || '⬡'}</span>
369-
{column.label || 'Tech Tree'}
370-
</h2>
371-
</div>
372-
</div>
432+
<VizHeader
433+
panel={column.panel}
434+
label={column.label || 'Tech Tree'}
435+
icon={column.icon || '⬡'}
436+
/>
373437
<div className="viz-panel-content">
374438
<TechTreeChart
375439
limit={25}
@@ -390,7 +454,7 @@ export function CanvasRenderer({
390454
)
391455
}
392456
},
393-
[data.commits, selection.selectedCommit, handlers]
457+
[data.commits, selection.selectedCommit, handlers, activeCanvas, setColumnPanel]
394458
)
395459

396460
// Render an editor panel based on column config

app/components/panels/editor/MailmapDetailsPanel.tsx

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -311,23 +311,56 @@ export function MailmapDetailsPanel({
311311
{/* Meta grid like other detail panels */}
312312
<div className="detail-meta-grid">
313313
<div className="detail-meta-item">
314-
<span className="meta-label">Mapped</span>
315-
<span className="meta-value">{stats.mappedEntries}</span>
316-
</div>
317-
<div className="detail-meta-item">
318-
<span className="meta-label">Raw Unique</span>
314+
<span className="meta-label">Git Authors</span>
319315
<span className="meta-value">{stats.rawUnique}</span>
320316
</div>
321317
<div className="detail-meta-item">
322-
<span className="meta-label">Unique</span>
318+
<span className="meta-label">After Mapping</span>
323319
<span className="meta-value">{stats.uniqueWithMapping}</span>
324320
</div>
321+
<div className="detail-meta-item">
322+
<span className="meta-label">Entries</span>
323+
<span className="meta-value">{stats.mappedEntries}</span>
324+
</div>
325325
</div>
326326

327327
<p className="mailmap-hint">
328328
Drag one user onto another to combine them. The drop target becomes the canonical name.
329329
</p>
330330

331+
{/* Existing .mailmap entries */}
332+
{existingMailmap.length > 0 && (
333+
<div className="mailmap-section">
334+
<h4>Current .mailmap ({existingMailmap.length} entries)</h4>
335+
<table className="mailmap-table">
336+
<thead>
337+
<tr>
338+
<th>Canonical</th>
339+
<th>Alias</th>
340+
<th></th>
341+
</tr>
342+
</thead>
343+
<tbody>
344+
{existingMailmap.map((entry, idx) => (
345+
<tr key={`entry-${idx}`}>
346+
<td>{entry.canonicalName} &lt;{entry.canonicalEmail}&gt;</td>
347+
<td>{entry.aliasName ? `${entry.aliasName} ` : ''}&lt;{entry.aliasEmail}&gt;</td>
348+
<td>
349+
<button
350+
className="mailmap-delete-btn"
351+
onClick={() => handleDeleteEntry(entry)}
352+
title="Remove entry"
353+
>
354+
×
355+
</button>
356+
</td>
357+
</tr>
358+
))}
359+
</tbody>
360+
</table>
361+
</div>
362+
)}
363+
331364
{/* Pending Merges */}
332365
{pendingMerges.length > 0 && (
333366
<div className="mailmap-section">
@@ -370,39 +403,6 @@ export function MailmapDetailsPanel({
370403
))}
371404
</div>
372405
</div>
373-
374-
{/* Existing .mailmap entries */}
375-
{existingMailmap.length > 0 && (
376-
<div className="mailmap-section">
377-
<h4>Current .mailmap ({existingMailmap.length} entries)</h4>
378-
<table className="mailmap-table">
379-
<thead>
380-
<tr>
381-
<th>Canonical</th>
382-
<th>Alias</th>
383-
<th></th>
384-
</tr>
385-
</thead>
386-
<tbody>
387-
{existingMailmap.map((entry, idx) => (
388-
<tr key={`entry-${idx}`}>
389-
<td>{entry.canonicalName} &lt;{entry.canonicalEmail}&gt;</td>
390-
<td>{entry.aliasName ? `${entry.aliasName} ` : ''}&lt;{entry.aliasEmail}&gt;</td>
391-
<td>
392-
<button
393-
className="mailmap-delete-btn"
394-
onClick={() => handleDeleteEntry(entry)}
395-
title="Remove entry"
396-
>
397-
×
398-
</button>
399-
</td>
400-
</tr>
401-
))}
402-
</tbody>
403-
</table>
404-
</div>
405-
)}
406406
</div>
407407
)
408408
}

app/components/panels/editor/RepoDetailPanel.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,6 @@ export function RepoDetailPanel({
101101
)}
102102
</div>
103103

104-
{/* Info section */}
105-
{repo.isCurrent ? (
106-
<div className="detail-info-section">
107-
<div className="info-note">
108-
This is the currently open repository. Use the panels to explore branches, PRs, and worktrees.
109-
</div>
110-
</div>
111-
) : (
112-
<div className="detail-info-section">
113-
<div className="info-note">
114-
Open this repository in Ledger to view its branches, pull requests, and worktrees.
115-
</div>
116-
</div>
117-
)}
118104
</div>
119105
)
120106
}

app/components/panels/list/BranchList.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ export function BranchList({
117117
? 'No branches match filter'
118118
: isRemote ? 'No remote branches' : 'No local branches'
119119

120+
// Build active filter label
121+
const activeFilterParts: string[] = []
122+
if (search.trim()) activeFilterParts.push(`"${search.trim()}"`)
123+
if (filter !== 'all') activeFilterParts.push(filter === 'local-only' ? 'Local Only' : 'Unmerged')
124+
const activeFilter = activeFilterParts.length > 0 ? activeFilterParts.join(' · ') : undefined
125+
120126
return (
121127
<div className={`list-panel branch-list-panel ${isRemote ? 'remote' : 'local'}`}>
122128
<ListPanelHeader
@@ -125,6 +131,7 @@ export function BranchList({
125131
count={filteredBranches.length}
126132
controlsOpen={controlsOpen}
127133
onToggleControls={() => setControlsOpen(!controlsOpen)}
134+
activeFilter={activeFilter}
128135
/>
129136

130137
{/* Controls */}

app/components/panels/list/CommitList.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ export function CommitList({
129129
? 'No commits match filter'
130130
: 'No commits'
131131

132+
// Build active filter label
133+
const activeFilterParts: string[] = []
134+
if (search.trim()) activeFilterParts.push(`"${search.trim()}"`)
135+
if (filter !== 'all') activeFilterParts.push(filter === 'branch-heads' ? 'Branch Heads' : 'Unmerged')
136+
const activeFilter = activeFilterParts.length > 0 ? activeFilterParts.join(' · ') : undefined
137+
132138
return (
133139
<div className="list-panel commits-list-panel">
134140
<ListPanelHeader
@@ -138,6 +144,7 @@ export function CommitList({
138144
controlsOpen={controlsOpen}
139145
onToggleControls={() => setControlsOpen(!controlsOpen)}
140146
badge={currentBranch ? <code className="commit-hash branch-badge">{currentBranch}</code> : undefined}
147+
activeFilter={activeFilter}
141148
/>
142149

143150
{/* Controls */}

app/components/panels/list/ListPanelHeader.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
* Features:
55
* - Icon + label + count badge
66
* - Optional badge (e.g., branch name)
7-
* - Clickable to toggle controls
8-
* - Chevron indicator for open/closed state
7+
* - Funnel icon to toggle filter controls
8+
* - Active filter indicator badge
99
*/
1010

1111
import type { ReactNode } from 'react'
@@ -18,6 +18,8 @@ interface ListPanelHeaderProps {
1818
onToggleControls: () => void
1919
/** Optional badge element (e.g., current branch name) */
2020
badge?: ReactNode
21+
/** Active filter label to display when filter is non-default */
22+
activeFilter?: string
2123
}
2224

2325
export function ListPanelHeader({
@@ -27,21 +29,37 @@ export function ListPanelHeader({
2729
controlsOpen,
2830
onToggleControls,
2931
badge,
32+
activeFilter,
3033
}: ListPanelHeaderProps) {
3134
return (
32-
<div
33-
className={`column-header clickable-header ${controlsOpen ? 'open' : ''}`}
34-
onClick={onToggleControls}
35-
>
35+
<div className="column-header list-header">
3636
<div className="column-title">
3737
<h2>
3838
{icon && <span className="column-icon">{icon}</span>}
3939
{label}
4040
{badge}
4141
</h2>
42-
<span className={`header-chevron ${controlsOpen ? 'open' : ''}`}></span>
4342
</div>
44-
<span className="count-badge">{count}</span>
43+
<div className="header-actions">
44+
{activeFilter && (
45+
<span className="active-filter-badge" title={`Filtered: ${activeFilter}`}>
46+
{activeFilter}
47+
</span>
48+
)}
49+
<button
50+
className={`header-filter-btn ${controlsOpen ? 'active' : ''}`}
51+
onClick={(e) => {
52+
e.stopPropagation()
53+
onToggleControls()
54+
}}
55+
title="Toggle filters"
56+
>
57+
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
58+
<path d="M0 1h10L6 5v4L4 10V5L0 1z" />
59+
</svg>
60+
</button>
61+
<span className="count-badge">{count}</span>
62+
</div>
4563
</div>
4664
)
4765
}

0 commit comments

Comments
 (0)