feat(ui): redesign recent commands display and fix terminal visibility
All checks were successful
Beta Release / beta (push) Successful in 44s

- Dashboard: add frequency bars for top commands, click-to-copy, time display
- Shell: switch from display:none to visibility:hidden for terminal containers
- CSS: restyle command list with improved hover states and copy indicators

💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
This commit is contained in:
Augustin
2026-04-24 16:53:59 +02:00
parent cbbb224725
commit c91931f42f
3 changed files with 58 additions and 41 deletions

View File

@@ -197,26 +197,34 @@ export default function Dashboard({ api, refreshRef }) {
</div>
{/* Recent Commands */}
<div className="dash-card">
<div className="dash-card dash-cmd-card">
<div className="dash-card-head">
<span className="dash-label">Recent Commands</span>
<span className="dash-count">{recentUnique.length}</span>
</div>
{topCmds.length > 0 && (
<div className="dash-cmd-top">
<div className="dash-cmd-freq">
<span className="dash-cmd-freq-title">Most used</span>
{topCmds.map((c, i) => (
<div key={i} className={'dash-cmd-chip' + (copiedIdx === i ? ' dash-cmd-chip-copied' : '')} onClick={() => { navigator.clipboard.writeText(c.cmd); setCopiedIdx(i); setTimeout(() => setCopiedIdx(-1), 1200); }}>
<span className="dash-cmd-chip-name">{copiedIdx === i ? '✓ Copié' : c.cmd}</span>
<span className="dash-cmd-chip-count">{c.count}×</span>
<div key={i} className="dash-cmd-freq-row" onClick={() => copyCmd(c.cmd, `top-${i}`)} title={c.cmd}>
<span className="dash-cmd-freq-name">{copiedSet.has(`top-${i}`) ? '✓ Copié' : c.cmd}</span>
<div className="dash-cmd-freq-bar-wrap">
<div className="dash-cmd-freq-bar" style={{ width: `${(c.count / maxCount) * 100}%` }} />
</div>
<span className="dash-cmd-freq-count">{c.count}×</span>
</div>
))}
</div>
)}
<div className="dash-cmd-list">
{recentCmds.length === 0 && <span className="dash-empty">No history</span>}
{recentCmds.map((c, i) => (
<div key={i} className="dash-cmd-row" title={c.cmd}>
<span className="dash-cmd-shell">{c.shell}</span>
<span className="dash-cmd-text">{c.cmd.length > 45 ? c.cmd.slice(0, 42) + '...' : c.cmd}</span>
{recentUnique.length === 0 && <span className="dash-empty">No history</span>}
{recentUnique.map((c, i) => (
<div key={i} className="dash-cmd-row" onClick={() => copyCmd(c.cmd, `list-${i}`)} title={c.cmd + ' · click to copy'}>
<div className="dash-cmd-left">
<span className="dash-cmd-text">{c.cmd.length > 38 ? c.cmd.slice(0, 35) + '...' : c.cmd}</span>
<span className="dash-cmd-time">{relativeTime(c.ts)}</span>
</div>
<span className="dash-cmd-copy">{copiedSet.has(`list-${i}`) ? '✓' : '⎘'}</span>
</div>
))}
</div>

View File

@@ -400,7 +400,7 @@ export default function Shell({ api }) {
const onResize = () => {
const el = document.getElementById(`terminal-${tabId}`)
if (el && el.offsetParent !== null) {
if (el && el.style.visibility !== 'hidden' && el.style.position !== 'absolute') {
fitAddon.fit()
}
}
@@ -438,24 +438,26 @@ export default function Shell({ api }) {
const tryInit = (attempt) => {
if (cancelled || attempt > 20) return
const shellCol = document.querySelector('.shell-terminal-col')
if (!shellCol || shellCol.offsetParent === null) {
if (!shellCol) {
pending.push(setTimeout(() => tryInit(attempt + 1), 150))
return
}
const container = document.getElementById(`terminal-${tab.id}`)
if (!container || container.offsetHeight === 0) {
if (!container) {
pending.push(setTimeout(() => tryInit(attempt + 1), 100))
return
}
if (!tabsRef.current[tab.id]) {
initTerminal(tab.id, tab)
}
if (activeTab === tab.id) {
requestAnimationFrame(() => {
if (cancelled) return
const entry = tabsRef.current[tab.id]
if (entry) entry.fitAddon.fit()
})
}
}
tryInit(0)
return () => {
@@ -470,7 +472,7 @@ export default function Shell({ api }) {
const entry = tabsRef.current[tab.id]
if (entry) {
const el = document.getElementById(`terminal-${tab.id}`)
if (el && el.offsetParent !== null) {
if (el && el.style.visibility !== 'hidden') {
entry.fitAddon.fit()
}
}
@@ -839,7 +841,10 @@ export default function Shell({ api }) {
key={tab.id}
id={`terminal-${tab.id}`}
className="shell-xterm-instance"
style={{ display: activeTab === tab.id ? 'block' : 'none' }}
style={activeTab === tab.id
? { visibility: 'visible', pointerEvents: 'auto' }
: { visibility: 'hidden', pointerEvents: 'none' }
}
/>
))}
</div>

View File

@@ -691,34 +691,38 @@ input::placeholder { color: var(--text-disabled); }
}
/* Commands */
.dash-cmd-list { display: flex; flex-direction: column; gap: 3px; max-height: 270px; overflow-y: auto; }
.dash-cmd-card .dash-cmd-list { max-height: 220px; }
.dash-cmd-list { display: flex; flex-direction: column; gap: 2px; overflow-y: auto; }
.dash-cmd-row {
display: flex; align-items: center; gap: 6px;
padding: 3px 0; overflow: hidden;
}
.dash-cmd-shell {
font-size: 9px; font-family: var(--font-mono); color: var(--text-disabled);
background: var(--bg-input); padding: 1px 4px; border-radius: 3px;
text-transform: uppercase; flex-shrink: 0;
display: flex; align-items: center; justify-content: space-between; gap: 8px;
padding: 5px 8px; border-radius: var(--radius-sm);
background: var(--bg-surface); cursor: pointer;
transition: background 0.12s;
}
.dash-cmd-row:hover { background: var(--accent-bg); }
.dash-cmd-left { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.dash-cmd-text {
font-size: 11px; font-family: var(--font-mono); color: var(--text-secondary);
font-size: 11px; font-family: var(--font-mono); color: var(--text-primary);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
flex: 1; min-width: 0;
}
.dash-cmd-time { font-size: 9px; color: var(--text-disabled); }
.dash-cmd-copy { font-size: 13px; color: var(--text-disabled); flex-shrink: 0; }
.dash-cmd-row:hover .dash-cmd-copy { color: var(--accent); }
.dash-cmd-freq { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.dash-cmd-freq-title { font-size: 10px; font-weight: 700; text-transform: uppercase; color: var(--text-disabled); letter-spacing: 0.05em; margin-bottom: 2px; }
.dash-cmd-freq-row {
display: flex; align-items: center; gap: 8px; cursor: pointer;
padding: 3px 4px; border-radius: var(--radius-sm);
transition: background 0.12s;
}
.dash-cmd-freq-row:hover { background: var(--accent-bg); }
.dash-cmd-freq-name { font-size: 12px; font-weight: 600; font-family: var(--font-mono); color: var(--text-primary); width: 100px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-cmd-freq-bar-wrap { flex: 1; height: 6px; background: var(--bg-input); border-radius: 3px; overflow: hidden; }
.dash-cmd-freq-bar { height: 100%; background: var(--accent); border-radius: 3px; transition: width 0.3s ease; }
.dash-cmd-freq-count { font-size: 10px; font-family: var(--font-mono); color: var(--accent); width: 28px; text-align: right; flex-shrink: 0; }
.dash-cmd-top { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.dash-cmd-chip {
display: flex; align-items: center; gap: 6px;
padding: 6px 12px; border-radius: var(--radius);
background: var(--bg-surface); border: 1px solid var(--border);
cursor: pointer; transition: all 0.15s;
}
.dash-cmd-chip:hover { border-color: var(--accent-dim); background: var(--accent-bg); }
.dash-cmd-chip-copied { border-color: var(--accent) !important; background: var(--accent-bg) !important; }
.dash-cmd-chip-copied .dash-cmd-chip-name { color: var(--accent); }
.dash-cmd-chip-name { font-size: 13px; font-weight: 700; font-family: var(--font-mono); color: var(--text-primary); }
.dash-cmd-chip-count { font-size: 10px; font-family: var(--font-mono); color: var(--accent); }
/* Services */
.dash-services { display: flex; flex-direction: column; gap: 6px; }