Compare commits

...

5 Commits

Author SHA1 Message Date
Augustin
3cf701b002 fix(terminal): improve tab visibility checks and positioning
All checks were successful
Beta Release / beta (push) Successful in 48s
- Add null check for container before accessing offsetHeight
- Validate activeTabRef during initialization and fit operations
- Check for display:none as visibility indicator
- Simplify useEffect dependency array
- Use absolute positioning for terminal wrapper/instance

💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
2026-04-24 17:59:48 +02:00
Augustin
3a09e0e0c2 fix(ui): adjust global CSS styles
All checks were successful
Beta Release / beta (push) Successful in 45s
💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
2026-04-24 17:38:21 +02:00
Augustin
47fa2e01bb fix(terminal): use display:none instead of visibility for tab hiding
All checks were successful
Beta Release / beta (push) Successful in 49s
Replace visibility-based hiding with display property for reliable tab
detection. Use offsetParent and offsetHeight checks instead of style
properties to properly detect hidden terminals.

💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
2026-04-24 17:23:54 +02:00
Augustin
401292ec5b feat(ui): refactor copy state to Set and add helper functions
All checks were successful
Beta Release / beta (push) Successful in 46s
- Change copiedIdx (number) to copiedSet (Set) for tracking multiple copied items
- Add copyCmd function to handle clipboard and timeout cleanup
- Add relativeTime function for displaying relative timestamps

💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
2026-04-24 17:04:38 +02:00
Augustin
199a7e409a feat(ui): add recentUnique to deduplicate recent commands in Dashboard
All checks were successful
Beta Release / beta (push) Successful in 47s
💘 Generated with Crush

Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
2026-04-24 17:01:08 +02:00
3 changed files with 47 additions and 20 deletions

View File

@@ -43,7 +43,7 @@ export default function Dashboard({ api, refreshRef }) {
const [recentCmds, setRecentCmds] = useState([])
const [processes, setProcesses] = useState([])
const [metrics, setMetrics] = useState(null)
const [copiedIdx, setCopiedIdx] = useState(-1)
const [copiedSet, setCopiedSet] = useState(new Set())
const cpuRef = useRef([])
const memRef = useRef([])
const netRxRef = useRef([])
@@ -109,6 +109,32 @@ export default function Dashboard({ api, refreshRef }) {
.map(([cmd, count]) => ({ cmd, count }))
})()
const maxCount = topCmds.length > 0 ? topCmds[0].count : 1
const copyCmd = (cmd, key) => {
navigator.clipboard.writeText(cmd)
setCopiedSet(prev => new Set(prev).add(key))
setTimeout(() => setCopiedSet(prev => { const next = new Set(prev); next.delete(key); return next }), 1500)
}
const relativeTime = (ts) => {
if (!ts) return ''
const diff = Math.floor((Date.now() - new Date(ts).getTime()) / 1000)
if (diff < 60) return `${diff}s`
if (diff < 3600) return `${Math.floor(diff / 60)}m`
if (diff < 86400) return `${Math.floor(diff / 3600)}h`
return `${Math.floor(diff / 86400)}d`
}
const recentUnique = (() => {
const seen = new Set()
return recentCmds.filter(c => {
if (seen.has(c.cmd)) return false
seen.add(c.cmd)
return true
})
})()
return (
<div className="dash-grid">
{/* CPU */}

View File

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

View File

@@ -382,12 +382,12 @@ input::placeholder { color: var(--text-disabled); }
}
.shell-menu-divider { height: 1px; background: var(--border); margin: 4px 6px; }
.shell-xterm-wrapper { flex: 1; background: var(--bg); overflow: hidden; position: relative; }
.shell-xterm-wrapper { flex: 1; height: 100%; background: var(--bg); overflow: hidden; position: relative; }
.shell-xterm-instance {
position: absolute; inset: 0; padding: 4px;
display: block !important;
position: absolute;
inset: 0;
}
.shell-xterm-instance .xterm { height: 100%; padding: 4px; }
.shell-xterm-instance .xterm { height: 100%; }
.connection-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.connection-dot.on { background: var(--success); box-shadow: 0 0 6px var(--success); }