diff --git a/README.md b/README.md index 87061a48..83a0331e 100644 --- a/README.md +++ b/README.md @@ -675,6 +675,19 @@ When developing the web interface, you often need to test changes on external de - **Firewall**: macOS may prompt to allow incoming connections - click "Allow" - **Auto-rebuild**: Changes to the web code are automatically rebuilt, but you need to manually refresh the browser +##### Pasting on Mobile Devices + +When using VibeTunnel on mobile browsers (Safari, Chrome), pasting works differently than on desktop: + +**To paste on mobile:** +1. Press the paste button on the keyboard toolbar +2. A white input box will appear +3. Long-press inside the white box to bring up the paste menu +4. Select "Paste" from the menu +5. The text will be pasted into your terminal session + +**Note**: Due to browser security restrictions on non-HTTPS connections, the paste API is limited on mobile devices. The white input box is a workaround that allows clipboard access through the browser's native paste functionality. + #### Future: Hot Module Replacement For true hot module replacement without manual refresh, see our [Vite migration plan](docs/vite-plan.md) which would provide: diff --git a/mac/VibeTunnel/Presentation/Views/Welcome/ControlAgentArmyPageView.swift b/mac/VibeTunnel/Presentation/Views/Welcome/ControlAgentArmyPageView.swift index b7375531..cd95cca2 100644 --- a/mac/VibeTunnel/Presentation/Views/Welcome/ControlAgentArmyPageView.swift +++ b/mac/VibeTunnel/Presentation/Views/Welcome/ControlAgentArmyPageView.swift @@ -52,14 +52,20 @@ struct ControlAgentArmyPageView: View { .cornerRadius(6) } - Text( - "Session titles appear in the menu bar and terminal windows.\nUse the dashboard to rename sessions manually, or use the magic wand with Claude/Gemini." - ) - .font(.caption) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - .frame(maxWidth: 420) - .fixedSize(horizontal: false, vertical: true) + VStack(spacing: 8) { + Text( + "Session titles appear in the menu bar and terminal windows.\nUse the dashboard to rename sessions manually, or use the magic wand with Claude/Gemini." + ) + .font(.caption) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .frame(maxWidth: 420) + .fixedSize(horizontal: false, vertical: true) + + Link("Learn more", destination: URL(string: "https://steipete.me/posts/command-your-claude-code-army-reloaded")!) + .font(.caption) + .foregroundColor(.accentColor) + } } .padding(.vertical, 12) } diff --git a/mac/VibeTunnel/Presentation/Views/Welcome/ProjectFolderPageView.swift b/mac/VibeTunnel/Presentation/Views/Welcome/ProjectFolderPageView.swift index 53270447..53623c50 100644 --- a/mac/VibeTunnel/Presentation/Views/Welcome/ProjectFolderPageView.swift +++ b/mac/VibeTunnel/Presentation/Views/Welcome/ProjectFolderPageView.swift @@ -20,122 +20,85 @@ struct ProjectFolderPageView: View { } var body: some View { - VStack(spacing: 24) { - // Title and description - VStack(spacing: 12) { + VStack(spacing: 30) { + VStack(spacing: 16) { Text("Choose Your Project Folder") - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(.primary) + .font(.largeTitle) + .fontWeight(.semibold) Text( "Select the folder where you keep your projects. VibeTunnel will use this for quick access and repository discovery." ) - .font(.system(size: 14)) + .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.center) - .frame(maxWidth: 400) - } + .frame(maxWidth: 480) + .fixedSize(horizontal: false, vertical: true) - // Folder picker section - VStack(alignment: .leading, spacing: 12) { - Text("Project Folder") - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.secondary) + // Folder and repository section + VStack(spacing: 16) { + // Folder picker + HStack { + Text(selectedPath.isEmpty ? "~/" : selectedPath) + .font(.system(size: 13)) + .foregroundColor(selectedPath.isEmpty ? .secondary : .primary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color(NSColor.controlBackgroundColor)) + .cornerRadius(6) - HStack { - Text(selectedPath.isEmpty ? "~/" : selectedPath) - .font(.system(size: 13)) - .foregroundColor(selectedPath.isEmpty ? .secondary : .primary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(Color(NSColor.controlBackgroundColor)) - .cornerRadius(6) - - Button("Choose...") { - showFolderPicker() + Button("Choose...") { + showFolderPicker() + } + .buttonStyle(.bordered) } - .buttonStyle(.bordered) - } + .frame(width: 350) - // Repository preview - if !selectedPath.isEmpty { - VStack(alignment: .leading, spacing: 8) { + // Repository count + if !selectedPath.isEmpty { HStack { - Text("Discovered Repositories") - .font(.system(size: 12, weight: .medium)) + Image(systemName: "folder.badge.gearshape") + .font(.system(size: 12)) .foregroundColor(.secondary) - + if isScanning { - ProgressView() - .scaleEffect(0.5) - .frame(width: 16, height: 16) + Text("Scanning...") + .font(.system(size: 12)) + .foregroundColor(.secondary) + } else if discoveredRepos.isEmpty { + Text("No repositories found") + .font(.system(size: 12)) + .foregroundColor(.secondary) + } else { + Text("\(discoveredRepos.count) repositor\(discoveredRepos.count == 1 ? "y" : "ies") found") + .font(.system(size: 12)) + .foregroundColor(.primary) } - + Spacer() } - - ScrollView { - VStack(alignment: .leading, spacing: 4) { - if discoveredRepos.isEmpty && !isScanning { - Text("No repositories found in this folder") - .font(.system(size: 11)) - .foregroundColor(.secondary) - .italic() - .padding(.vertical, 8) - } else { - ForEach(discoveredRepos) { repo in - HStack { - Image(systemName: "folder.badge.gearshape") - .font(.system(size: 11)) - .foregroundColor(.secondary) - - Text(repo.name) - .font(.system(size: 11)) - .lineLimit(1) - - Spacer() - } - .padding(.vertical, 2) - } - } - } - } - .frame(maxHeight: 100) - .padding(8) + .padding(.horizontal, 10) + .padding(.vertical, 6) .background(Color(NSColor.controlBackgroundColor).opacity(0.5)) .cornerRadius(6) + .frame(width: 350) } + // Tip + HStack(alignment: .top, spacing: 6) { + Text("You can change this later in Settings → Application → Repository") + .font(.system(size: 11)) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + } + .frame(maxWidth: 350) + .padding(.top, 8) } } - .frame(maxWidth: 400) - - // Tips - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .top, spacing: 8) { - Image(systemName: "lightbulb") - .font(.system(size: 12)) - .foregroundColor(.orange) - - Text("You can change this later in Settings → Application → Repository Base Path") - .font(.system(size: 11)) - .foregroundColor(.secondary) - } - - HStack(alignment: .top, spacing: 8) { - Image(systemName: "info.circle") - .font(.system(size: 12)) - .foregroundColor(.blue) - - Text("VibeTunnel will scan up to 3 levels deep for Git repositories") - .font(.system(size: 11)) - .foregroundColor(.secondary) - } - } - .frame(maxWidth: 400) - .padding(.top, 12) + Spacer() } - .padding(.horizontal, 40) + .padding() .onAppear { selectedPath = repositoryBasePath if !selectedPath.isEmpty { @@ -190,7 +153,7 @@ struct ProjectFolderPageView: View { let repos = await findGitRepositories(in: expandedPath, maxDepth: 3) await MainActor.run { - discoveredRepos = repos.prefix(10).map { path in + discoveredRepos = repos.map { path in RepositoryInfo(name: URL(fileURLWithPath: path).lastPathComponent, path: path) } isScanning = false diff --git a/mac/VibeTunnel/Presentation/Views/Welcome/RequestPermissionsPageView.swift b/mac/VibeTunnel/Presentation/Views/Welcome/RequestPermissionsPageView.swift index 4f9ef278..d4b34241 100644 --- a/mac/VibeTunnel/Presentation/Views/Welcome/RequestPermissionsPageView.swift +++ b/mac/VibeTunnel/Presentation/Views/Welcome/RequestPermissionsPageView.swift @@ -73,7 +73,7 @@ struct RequestPermissionsPageView: View { .foregroundColor(.secondary) } .font(.body) - .frame(maxWidth: 250) + .frame(maxWidth: 250, alignment: .leading) .frame(height: 32) } else { Button("Grant Automation Permission") { @@ -93,7 +93,7 @@ struct RequestPermissionsPageView: View { .foregroundColor(.secondary) } .font(.body) - .frame(maxWidth: 250) + .frame(maxWidth: 250, alignment: .leading) .frame(height: 32) } else { Button("Grant Accessibility Permission") { @@ -113,7 +113,7 @@ struct RequestPermissionsPageView: View { .foregroundColor(.secondary) } .font(.body) - .frame(maxWidth: 250) + .frame(maxWidth: 250, alignment: .leading) .frame(height: 32) } else { Button("Grant Screen Recording Permission") { diff --git a/web/src/client/components/screencap-view.ts b/web/src/client/components/screencap-view.ts index 634139ff..51b0eede 100644 --- a/web/src/client/components/screencap-view.ts +++ b/web/src/client/components/screencap-view.ts @@ -1336,7 +1336,7 @@ export class ScreencapView extends LitElement { // Show overlay when not capturing or waiting to start return html` -