mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-20 13:45:54 +00:00
feat: Add activity status display to menu bar session entries
- Display activity status (e.g., 'Browsing', 'Considering') below each session - Show status inline with working directory path, separated by dot - Match visual style from web frontend with orange status text - Add compact path display with ~ for home directory
This commit is contained in:
parent
4e5f100074
commit
6c33e16565
5 changed files with 443 additions and 97 deletions
|
|
@ -325,27 +325,46 @@ struct SessionRowView: View {
|
|||
// Focus the terminal window for this session
|
||||
WindowTracker.shared.focusWindow(for: session.key)
|
||||
}, label: {
|
||||
HStack {
|
||||
Text(" • \(sessionName)")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
// Main session row
|
||||
HStack {
|
||||
Text(" • \(sessionName)")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
|
||||
Spacer()
|
||||
Spacer()
|
||||
|
||||
if let claudeStatus = claudeStatus {
|
||||
Text(claudeStatus)
|
||||
if let pid = session.value.pid {
|
||||
Text("PID: \(pid)")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Activity status and path row
|
||||
HStack(spacing: 4) {
|
||||
Text(" ")
|
||||
.font(.system(size: 11))
|
||||
|
||||
if let activityStatus = activityStatus {
|
||||
Text(activityStatus)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Text("·")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary.opacity(0.5))
|
||||
}
|
||||
|
||||
Text(compactPath)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
|
||||
if let pid = session.value.pid {
|
||||
Text("PID: \(pid)")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
.truncationMode(.middle)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
|
|
@ -392,13 +411,32 @@ struct SessionRowView: View {
|
|||
return name
|
||||
}
|
||||
|
||||
private var claudeStatus: String? {
|
||||
if let specificStatus = session.value.activityStatus?.specificStatus,
|
||||
specificStatus.app == "claude" {
|
||||
private var activityStatus: String? {
|
||||
if let specificStatus = session.value.activityStatus?.specificStatus {
|
||||
return specificStatus.status
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private var compactPath: String {
|
||||
let path = session.value.workingDir
|
||||
let homeDir = NSHomeDirectory()
|
||||
|
||||
// Replace home directory with ~
|
||||
if path.hasPrefix(homeDir) {
|
||||
let relativePath = String(path.dropFirst(homeDir.count))
|
||||
return "~" + relativePath
|
||||
}
|
||||
|
||||
// For other paths, show last two components
|
||||
let components = (path as NSString).pathComponents
|
||||
if components.count > 2 {
|
||||
let lastTwo = components.suffix(2).joined(separator: "/")
|
||||
return ".../" + lastTwo
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Menu Button Style
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct SessionDetailView: View {
|
|||
.fontWeight(.bold)
|
||||
|
||||
HStack {
|
||||
Label("PID: \(session.pid)", systemImage: "number.circle.fill")
|
||||
Label("PID: \(session.pid ?? 0)", systemImage: "number.circle.fill")
|
||||
.font(.title3)
|
||||
|
||||
Spacer()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,315 @@ final class SessionMonitorTests {
|
|||
await monitor.refresh()
|
||||
}
|
||||
|
||||
// MARK: - Basic Functionality Tests
|
||||
// MARK: - JSON Decoding Tests
|
||||
|
||||
@Test("Decode valid session with all fields")
|
||||
func decodeValidSessionAllFields() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "test-session-123",
|
||||
"command": ["bash", "-l"],
|
||||
"name": "Test Session",
|
||||
"workingDir": "/Users/test",
|
||||
"status": "running",
|
||||
"exitCode": null,
|
||||
"startedAt": "2025-01-01T10:00:00.000Z",
|
||||
"lastModified": "2025-01-01T10:05:00.000Z",
|
||||
"pid": 12345,
|
||||
"initialCols": 80,
|
||||
"initialRows": 24,
|
||||
"activityStatus": {
|
||||
"isActive": true,
|
||||
"specificStatus": {
|
||||
"app": "claude",
|
||||
"status": "active"
|
||||
}
|
||||
},
|
||||
"source": "local"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let session = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
|
||||
#expect(session.id == "test-session-123")
|
||||
#expect(session.command == ["bash", "-l"])
|
||||
#expect(session.name == "Test Session")
|
||||
#expect(session.workingDir == "/Users/test")
|
||||
#expect(session.status == "running")
|
||||
#expect(session.exitCode == nil)
|
||||
#expect(session.startedAt == "2025-01-01T10:00:00.000Z")
|
||||
#expect(session.lastModified == "2025-01-01T10:05:00.000Z")
|
||||
#expect(session.pid == 12345)
|
||||
#expect(session.initialCols == 80)
|
||||
#expect(session.initialRows == 24)
|
||||
#expect(session.activityStatus?.isActive == true)
|
||||
#expect(session.activityStatus?.specificStatus?.app == "claude")
|
||||
#expect(session.activityStatus?.specificStatus?.status == "active")
|
||||
#expect(session.source == "local")
|
||||
#expect(session.isRunning == true)
|
||||
}
|
||||
|
||||
@Test("Decode session with minimal fields")
|
||||
func decodeSessionMinimalFields() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "minimal-session",
|
||||
"command": ["sh"],
|
||||
"workingDir": "/tmp",
|
||||
"status": "exited",
|
||||
"startedAt": "2025-01-01T09:00:00.000Z",
|
||||
"lastModified": "2025-01-01T09:30:00.000Z"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let session = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
|
||||
#expect(session.id == "minimal-session")
|
||||
#expect(session.command == ["sh"])
|
||||
#expect(session.name == nil)
|
||||
#expect(session.workingDir == "/tmp")
|
||||
#expect(session.status == "exited")
|
||||
#expect(session.exitCode == nil)
|
||||
#expect(session.pid == nil)
|
||||
#expect(session.initialCols == nil)
|
||||
#expect(session.initialRows == nil)
|
||||
#expect(session.activityStatus == nil)
|
||||
#expect(session.source == nil)
|
||||
#expect(session.isRunning == false)
|
||||
}
|
||||
|
||||
@Test("Decode session with command array bug reproduction")
|
||||
func decodeSessionCommandArrayBug() throws {
|
||||
// This test reproduces the exact bug where command was an array
|
||||
let json = """
|
||||
{
|
||||
"id": "bug-session",
|
||||
"command": ["claude", "session", "--continue"],
|
||||
"name": "Claude Session",
|
||||
"workingDir": "/Users/developer/project",
|
||||
"status": "running",
|
||||
"exitCode": null,
|
||||
"startedAt": "2025-01-01T12:00:00.000Z",
|
||||
"lastModified": "2025-01-01T12:15:00.000Z",
|
||||
"pid": 54321,
|
||||
"initialCols": 120,
|
||||
"initialRows": 40,
|
||||
"activityStatus": {
|
||||
"isActive": true,
|
||||
"specificStatus": {
|
||||
"app": "claude",
|
||||
"status": "thinking"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let session = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
|
||||
// Verify command array is properly decoded
|
||||
#expect(session.command == ["claude", "session", "--continue"])
|
||||
#expect(session.command.count == 3)
|
||||
#expect(session.command[0] == "claude")
|
||||
#expect(session.command[1] == "session")
|
||||
#expect(session.command[2] == "--continue")
|
||||
#expect(session.isRunning == true)
|
||||
}
|
||||
|
||||
@Test("Decode session array from API response")
|
||||
func decodeSessionArrayFromAPI() throws {
|
||||
let json = """
|
||||
[
|
||||
{
|
||||
"id": "session-1",
|
||||
"command": ["bash"],
|
||||
"workingDir": "/home/user1",
|
||||
"status": "running",
|
||||
"startedAt": "2025-01-01T10:00:00.000Z",
|
||||
"lastModified": "2025-01-01T10:05:00.000Z",
|
||||
"pid": 1001
|
||||
},
|
||||
{
|
||||
"id": "session-2",
|
||||
"command": ["python3", "script.py"],
|
||||
"workingDir": "/home/user2",
|
||||
"status": "exited",
|
||||
"exitCode": 0,
|
||||
"startedAt": "2025-01-01T09:00:00.000Z",
|
||||
"lastModified": "2025-01-01T09:30:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "session-3",
|
||||
"command": ["node", "server.js", "--port", "3000"],
|
||||
"name": "Dev Server",
|
||||
"workingDir": "/app",
|
||||
"status": "running",
|
||||
"startedAt": "2025-01-01T11:00:00.000Z",
|
||||
"lastModified": "2025-01-01T11:45:00.000Z",
|
||||
"pid": 2002,
|
||||
"initialCols": 100,
|
||||
"initialRows": 30
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let sessions = try JSONDecoder().decode([ServerSessionInfo].self, from: data)
|
||||
|
||||
#expect(sessions.count == 3)
|
||||
|
||||
// Verify first session
|
||||
#expect(sessions[0].id == "session-1")
|
||||
#expect(sessions[0].command == ["bash"])
|
||||
#expect(sessions[0].isRunning == true)
|
||||
#expect(sessions[0].pid == 1001)
|
||||
|
||||
// Verify second session
|
||||
#expect(sessions[1].id == "session-2")
|
||||
#expect(sessions[1].command == ["python3", "script.py"])
|
||||
#expect(sessions[1].isRunning == false)
|
||||
#expect(sessions[1].exitCode == 0)
|
||||
|
||||
// Verify third session
|
||||
#expect(sessions[2].id == "session-3")
|
||||
#expect(sessions[2].command == ["node", "server.js", "--port", "3000"])
|
||||
#expect(sessions[2].name == "Dev Server")
|
||||
#expect(sessions[2].isRunning == true)
|
||||
}
|
||||
|
||||
// MARK: - Edge Case Tests
|
||||
|
||||
@Test("Handle empty JSON array response")
|
||||
func handleEmptyArrayResponse() throws {
|
||||
let json = "[]"
|
||||
let data = json.data(using: .utf8)!
|
||||
let sessions = try JSONDecoder().decode([ServerSessionInfo].self, from: data)
|
||||
|
||||
#expect(sessions.isEmpty)
|
||||
}
|
||||
|
||||
@Test("Handle malformed JSON", .tags(.reliability))
|
||||
func handleMalformedJSON() {
|
||||
let malformedJson = """
|
||||
{
|
||||
"id": "broken",
|
||||
"command": "this should be an array",
|
||||
"workingDir": "/tmp",
|
||||
"status": "running"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = malformedJson.data(using: .utf8)!
|
||||
|
||||
#expect(throws: (any Error).self) {
|
||||
_ = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Handle missing required fields")
|
||||
func handleMissingRequiredFields() {
|
||||
let incompleteJson = """
|
||||
{
|
||||
"id": "incomplete",
|
||||
"workingDir": "/tmp"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = incompleteJson.data(using: .utf8)!
|
||||
|
||||
#expect(throws: (any Error).self) {
|
||||
_ = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Handle unexpected session status values")
|
||||
func handleUnexpectedStatus() throws {
|
||||
// The status field is just a string, so any value should work
|
||||
let json = """
|
||||
{
|
||||
"id": "weird-status",
|
||||
"command": ["bash"],
|
||||
"workingDir": "/tmp",
|
||||
"status": "zombie",
|
||||
"startedAt": "2025-01-01T10:00:00.000Z",
|
||||
"lastModified": "2025-01-01T10:00:00.000Z"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let session = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
|
||||
#expect(session.status == "zombie")
|
||||
#expect(session.isRunning == false) // Only "running" status means isRunning = true
|
||||
}
|
||||
|
||||
// MARK: - isRunning Calculation Tests
|
||||
|
||||
@Test("isRunning calculation for different statuses")
|
||||
func isRunningCalculation() throws {
|
||||
let statuses = [
|
||||
("running", true),
|
||||
("exited", false),
|
||||
("starting", false),
|
||||
("stopped", false),
|
||||
("crashed", false),
|
||||
("", false),
|
||||
("RUNNING", false), // Case sensitive
|
||||
("Running", false)
|
||||
]
|
||||
|
||||
for (status, expectedRunning) in statuses {
|
||||
let json = """
|
||||
{
|
||||
"id": "test-\(status)",
|
||||
"command": ["test"],
|
||||
"workingDir": "/tmp",
|
||||
"status": "\(status)",
|
||||
"startedAt": "2025-01-01T10:00:00.000Z",
|
||||
"lastModified": "2025-01-01T10:00:00.000Z"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let session = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
|
||||
#expect(session.isRunning == expectedRunning,
|
||||
"Status '\(status)' should result in isRunning=\(expectedRunning)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Remote Session Tests
|
||||
|
||||
@Test("Decode remote session with HQ mode fields")
|
||||
func decodeRemoteSession() throws {
|
||||
let json = """
|
||||
{
|
||||
"id": "remote-session-456",
|
||||
"command": ["ssh", "remote-server"],
|
||||
"name": "Remote SSH Session",
|
||||
"workingDir": "/home/remote",
|
||||
"status": "running",
|
||||
"startedAt": "2025-01-01T14:00:00.000Z",
|
||||
"lastModified": "2025-01-01T14:30:00.000Z",
|
||||
"pid": 8888,
|
||||
"source": "remote",
|
||||
"remoteId": "remote-123",
|
||||
"remoteName": "Production Server",
|
||||
"remoteUrl": "https://remote.example.com"
|
||||
}
|
||||
"""
|
||||
|
||||
let data = json.data(using: .utf8)!
|
||||
let session = try JSONDecoder().decode(ServerSessionInfo.self, from: data)
|
||||
|
||||
#expect(session.source == "remote")
|
||||
// Note: remoteId, remoteName, and remoteUrl are not part of ServerSessionInfo
|
||||
// They would need to be added if HQ mode support is needed
|
||||
}
|
||||
|
||||
// MARK: - Session Count Tests
|
||||
|
||||
@Test("Session count calculation")
|
||||
func sessionCount() {
|
||||
|
|
@ -22,7 +330,7 @@ final class SessionMonitorTests {
|
|||
#expect(monitor.sessionCount == 0)
|
||||
|
||||
// Note: Full integration tests would require a running server
|
||||
// These tests verify the basic functionality of SessionMonitor
|
||||
// or the ability to inject mock sessions
|
||||
}
|
||||
|
||||
// MARK: - Cache Behavior Tests
|
||||
|
|
@ -36,7 +344,6 @@ final class SessionMonitorTests {
|
|||
let cachedSessions = await monitor.getSessions()
|
||||
|
||||
// Verify we got a result (even if empty due to no server)
|
||||
// cachedSessions is non-optional, so just verify it's a dictionary
|
||||
#expect(cachedSessions.isEmpty || !cachedSessions.isEmpty)
|
||||
}
|
||||
|
||||
|
|
@ -55,19 +362,82 @@ final class SessionMonitorTests {
|
|||
#expect(type(of: initialSessions) == type(of: refreshedSessions))
|
||||
}
|
||||
|
||||
// MARK: - Error Handling Tests
|
||||
// MARK: - Mock API Response Tests
|
||||
|
||||
@Test("Error handling", .tags(.reliability))
|
||||
func errorHandling() async {
|
||||
// When server is not running, should handle gracefully
|
||||
_ = await monitor.getSessions()
|
||||
@Test("Parse real-world API response")
|
||||
func parseRealWorldResponse() throws {
|
||||
// This mimics an actual response from the server
|
||||
let realWorldJson = """
|
||||
[
|
||||
{
|
||||
"id": "20250101-100000-abc123",
|
||||
"command": ["claude", "session", "--continue", "20250101-095000-xyz789"],
|
||||
"name": "Claude Development Session",
|
||||
"workingDir": "/Users/developer/vibetunnel",
|
||||
"status": "running",
|
||||
"startedAt": "2025-01-01T10:00:00.123Z",
|
||||
"lastModified": "2025-01-01T10:45:32.456Z",
|
||||
"pid": 45678,
|
||||
"initialCols": 120,
|
||||
"initialRows": 40,
|
||||
"activityStatus": {
|
||||
"isActive": true,
|
||||
"specificStatus": {
|
||||
"app": "claude",
|
||||
"status": "editing"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "20250101-090000-def456",
|
||||
"command": ["pnpm", "run", "dev"],
|
||||
"name": "Development Server",
|
||||
"workingDir": "/Users/developer/vibetunnel/web",
|
||||
"status": "running",
|
||||
"startedAt": "2025-01-01T09:00:00.000Z",
|
||||
"lastModified": "2025-01-01T10:45:00.000Z",
|
||||
"pid": 34567,
|
||||
"initialCols": 80,
|
||||
"initialRows": 24
|
||||
},
|
||||
{
|
||||
"id": "20250101-083000-ghi789",
|
||||
"command": ["git", "log", "--oneline", "-10"],
|
||||
"workingDir": "/Users/developer/vibetunnel",
|
||||
"status": "exited",
|
||||
"exitCode": 0,
|
||||
"startedAt": "2025-01-01T08:30:00.000Z",
|
||||
"lastModified": "2025-01-01T08:30:05.000Z"
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
// Should have empty sessions, not crash
|
||||
#expect(monitor.sessions.isEmpty || !monitor.sessions.isEmpty)
|
||||
let data = realWorldJson.data(using: .utf8)!
|
||||
let sessions = try JSONDecoder().decode([ServerSessionInfo].self, from: data)
|
||||
|
||||
// Last error might be nil (if treating connection errors as expected)
|
||||
// or might contain error info
|
||||
#expect(monitor.lastError == nil || monitor.lastError != nil)
|
||||
#expect(sessions.count == 3)
|
||||
|
||||
// Verify Claude session
|
||||
let claudeSession = sessions[0]
|
||||
#expect(claudeSession.command.count == 4)
|
||||
#expect(claudeSession.command[0] == "claude")
|
||||
#expect(claudeSession.command[1] == "session")
|
||||
#expect(claudeSession.command[2] == "--continue")
|
||||
#expect(claudeSession.isRunning == true)
|
||||
#expect(claudeSession.activityStatus?.specificStatus?.app == "claude")
|
||||
|
||||
// Verify dev server session
|
||||
let devSession = sessions[1]
|
||||
#expect(devSession.command == ["pnpm", "run", "dev"])
|
||||
#expect(devSession.isRunning == true)
|
||||
#expect(devSession.pid == 34567)
|
||||
|
||||
// Verify exited session
|
||||
let gitSession = sessions[2]
|
||||
#expect(gitSession.command == ["git", "log", "--oneline", "-10"])
|
||||
#expect(gitSession.isRunning == false)
|
||||
#expect(gitSession.exitCode == 0)
|
||||
#expect(gitSession.pid == nil)
|
||||
}
|
||||
|
||||
// MARK: - Concurrent Access Tests
|
||||
|
|
@ -96,39 +466,6 @@ final class SessionMonitorTests {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Session Update Tests
|
||||
|
||||
@Test("Session updates are reflected")
|
||||
func sessionUpdates() async {
|
||||
// Get initial state
|
||||
_ = monitor.sessionCount
|
||||
|
||||
// Refresh to get latest
|
||||
await monitor.refresh()
|
||||
|
||||
// Count should be consistent with sessions dictionary
|
||||
#expect(monitor.sessionCount == monitor.sessions.count)
|
||||
#expect(monitor.sessionCount >= 0)
|
||||
}
|
||||
|
||||
// MARK: - Integration Tests
|
||||
|
||||
@Test("Session monitor integration", .tags(.integration))
|
||||
func integration() async {
|
||||
// Test the full flow
|
||||
await monitor.refresh()
|
||||
let sessions = await monitor.getSessions()
|
||||
|
||||
// Verify session structure if we have any
|
||||
for (sessionId, _) in sessions {
|
||||
// Session ID should be valid
|
||||
#expect(!sessionId.isEmpty)
|
||||
|
||||
// Note: ServerSessionInfo structure details would be validated here
|
||||
// if we had access to the actual session info fields
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Performance Tests
|
||||
|
||||
@Test("Cache performance", .tags(.performance))
|
||||
|
|
@ -148,4 +485,4 @@ final class SessionMonitorTests {
|
|||
// Cached access should be very fast
|
||||
#expect(elapsed < 0.1, "Cached access took too long: \(elapsed)s for 100 calls")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +56,7 @@ export class AppHeader extends LitElement {
|
|||
@hide-exited-change=${this.forwardEvent}
|
||||
@kill-all-sessions=${this.forwardEvent}
|
||||
@clean-exited-sessions=${this.forwardEvent}
|
||||
@open-file-browser=${this.forwardEvent}
|
||||
@open-settings=${this.forwardEvent}
|
||||
@logout=${this.forwardEvent}
|
||||
@navigate-to-list=${this.forwardEvent}
|
||||
|
|
|
|||
|
|
@ -146,14 +146,6 @@ export class SessionList extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private handleOpenFileBrowser() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('open-file-browser', {
|
||||
bubbles: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const filteredSessions = this.hideExited
|
||||
? this.sessions.filter((session) => session.status !== 'exited')
|
||||
|
|
@ -237,28 +229,6 @@ export class SessionList extends LitElement {
|
|||
`
|
||||
: html`
|
||||
<div class="${this.compactMode ? 'space-y-2' : 'session-flex-responsive'}">
|
||||
${
|
||||
this.compactMode
|
||||
? html`
|
||||
<!-- Browse Files button as special tab -->
|
||||
<div
|
||||
class="flex items-center gap-2 p-3 rounded-md cursor-pointer transition-all hover:bg-dark-bg-tertiary border border-dark-border bg-dark-bg-secondary"
|
||||
@click=${this.handleOpenFileBrowser}
|
||||
title="Browse Files (⌘O)"
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-mono text-accent-green truncate">
|
||||
📁 Browse Files
|
||||
</div>
|
||||
<div class="text-xs text-dark-text-muted truncate">Open file browser</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<span class="text-dark-text-muted text-xs">⌘O</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${repeat(
|
||||
filteredSessions,
|
||||
(session) => session.id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue