diff --git a/docs/iphone.md b/docs/iphone.md index 2a78588..f75b23c 100644 --- a/docs/iphone.md +++ b/docs/iphone.md @@ -12,6 +12,8 @@ Left out on the phone: the Server tab and running byre locally, the shell, file, `mac/phone.sh --simulator` builds an ad-hoc-signed app for the iOS simulator instead, at `mac/.build/phone/iphonesimulator/Byre.app`; install it with `xcrun simctl install ` and start it with `xcrun simctl launch run.fc.byre.ios`. The simulator ignores entitlements given to `codesign` and refuses to launch an ad-hoc app signed with restricted ones, so the script links `mac/phone/byre.entitlements` into the binary's `__TEXT,__entitlements` section, as Xcode does; without the iCloud entitlement CloudKit raises at launch. Its node signs in to Tailscale like any other: `xcrun simctl launch --console-pty` prints libtailscale's sign-in URL, which changes on every launch. +The simulator's window (Xcode's Device Hub) lists the app's own accessibility elements, so hands' `ax_tree` and `act` `press` drive the app without a mouse. Hands' mouse steps (`click`, `drag`, `scroll`) refuse that window, because Device Hub reports no process id; a gesture such as dragging the keyboard away needs mouse events posted with `CGEvent` instead. `xcrun simctl io booted screenshot --display LCD ` captures the app at full resolution. + ## Platform facts - CloudKit on iOS requires the `remote-notification` background mode in `Info.plist`, even though the app sets up no push of its own. diff --git a/mac/Sources/Byre/Format.swift b/mac/Sources/Byre/Format.swift index f7c19a8..b486a02 100644 --- a/mac/Sources/Byre/Format.swift +++ b/mac/Sources/Byre/Format.swift @@ -439,7 +439,7 @@ private struct WebButton: View { .font(Fonts.font(13, weight: primary ? 700 : 400)) .foregroundStyle(primary ? Palette.bg : danger ? Palette.danger : Palette.text) .padding(.horizontal, 12) - .padding(.vertical, 4) + .padding(.vertical, touchOnly ? 7 : 4) .background(primary ? fill : Palette.raised, in: RoundedRectangle(cornerRadius: 6)) .overlay(RoundedRectangle(cornerRadius: 6).stroke(primary ? fill : hover && enabled ? Palette.muted : Palette.border)) .opacity(enabled ? (configuration.isPressed ? 0.8 : 1) : 0.45) @@ -458,7 +458,9 @@ struct SmallLinkButtonStyle: ButtonStyle { .font(Fonts.font(11)) .foregroundStyle(color) .padding(.horizontal, 6) + .padding(.vertical, touchOnly ? 3 : 0) .overlay(RoundedRectangle(cornerRadius: 4).stroke(border)) + .padding(.vertical, touchOnly ? 5 : 0) .opacity(enabled ? 1 : 0.45) .contentShape(Rectangle()) } diff --git a/mac/Sources/Byre/Notes.swift b/mac/Sources/Byre/Notes.swift index 6847289..1c2942c 100644 --- a/mac/Sources/Byre/Notes.swift +++ b/mac/Sources/Byre/Notes.swift @@ -124,11 +124,9 @@ private struct NoteRow: View { @ViewState private var frame: CGRect = .zero var body: some View { - HStack(alignment: .firstTextBaseline, spacing: 12) { - Text(note.text) - .textSelection(.enabled) + HStack(alignment: .top, spacing: 12) { + MarkdownView(blocks: parseMarkdown(note.text)) .frame(maxWidth: .infinity, alignment: .leading) - .fixedSize(horizontal: false, vertical: true) Button("Delete") { Task { if !(await overlays.showConfirm(["Delete this note? Your lead stops applying it.", note.text], action: "Delete", danger: true, anchor: frame)) { diff --git a/mac/Sources/Byre/Phone.swift b/mac/Sources/Byre/Phone.swift index ac6c188..0251191 100644 --- a/mac/Sources/Byre/Phone.swift +++ b/mac/Sources/Byre/Phone.swift @@ -56,6 +56,7 @@ struct PhoneView: View { @Environment(Overlays.self) private var overlays @ViewState private var tab = PhoneTab.lead @ViewState private var path: [PhoneRoute] = [] + @ViewState private var typing = false var body: some View { VStack(spacing: 0) { @@ -77,36 +78,17 @@ struct PhoneView: View { } } .frame(maxWidth: .infinity, maxHeight: .infinity) - HStack(spacing: 4) { - tabButton("Lead", .lead) - HStack(spacing: 0) { - tabButton("Tasks", .tasks) - if !store.inbox.isEmpty { - Badge(count: store.inbox.count) - } - } - tabButton("More", .more) - Spacer(minLength: 0) - ConnectionMark() - Button { - tab = .tasks - path = [] - } label: { - Image(systemName: "list.bullet") - .font(Fonts.font(17, weight: 600)) - .foregroundStyle(Palette.accent) - .padding(.horizontal, 10) - .padding(.vertical, 8) - .contentShape(Rectangle()) - } - .buttonStyle(.plain) - .accessibilityLabel("Task list") - } - .padding(.horizontal, 12) - .overlay(alignment: .top) { - Rectangle().fill(Palette.border).frame(height: 1) + if !typing { + tabBar } } + .scrollDismissesKeyboard(.interactively) + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in + typing = true + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in + typing = false + } .overlay { OverlayHost() } @@ -159,6 +141,39 @@ struct PhoneView: View { } } + private var tabBar: some View { + HStack(spacing: 4) { + tabButton("Lead", .lead) + HStack(spacing: 0) { + tabButton("Tasks", .tasks) + if !store.inbox.isEmpty { + Badge(count: store.inbox.count) + } + } + tabButton("More", .more) + Spacer(minLength: 0) + ConnectionMark() + Button { + tab = .tasks + path = [] + } label: { + Image(systemName: "list.bullet") + .font(Fonts.font(17, weight: 600)) + .foregroundStyle(Palette.accent) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .accessibilityLabel("Task list") + } + .padding(.horizontal, 12) + .padding(.vertical, 4) + .overlay(alignment: .top) { + Rectangle().fill(Palette.border).frame(height: 1) + } + } + private func shown(_ which: PhoneTab, @ViewBuilder _ content: () -> some View) -> some View { content() .opacity(tab == which ? 1 : 0) diff --git a/mac/Sources/Byre/Server.swift b/mac/Sources/Byre/Server.swift index 33c51ea..7d29ff1 100644 --- a/mac/Sources/Byre/Server.swift +++ b/mac/Sources/Byre/Server.swift @@ -83,7 +83,7 @@ struct ChecksView: View { case "required": return "Required" case "agent": - return "One of claude, codex and agy required" + return "One of claude, codex or agy required" default: return "Optional" } diff --git a/mac/Sources/Byre/Switcher.swift b/mac/Sources/Byre/Switcher.swift index a44787f..ee4a94d 100644 --- a/mac/Sources/Byre/Switcher.swift +++ b/mac/Sources/Byre/Switcher.swift @@ -303,7 +303,9 @@ final class SwitcherModel { if store.inbox.contains(where: { $0.taskId == taskId }) { store.showQuestion(taskId) } else { - store.composerFocus = taskId + if !touchOnly { + store.composerFocus = taskId + } store.focusTask(taskId) } } diff --git a/mac/Sources/Byre/TranscriptBuilder.swift b/mac/Sources/Byre/TranscriptBuilder.swift index 60bdf80..c983531 100644 --- a/mac/Sources/Byre/TranscriptBuilder.swift +++ b/mac/Sources/Byre/TranscriptBuilder.swift @@ -1054,7 +1054,11 @@ private func toolTarget(_ input: (any Sendable)?, _ root: String) -> String { guard let path = (i?["file_path"] ?? i?["path"]) as? String else { return "" } - return relative(path, root) + let short = relative(path, root) + if short != path || !path.hasPrefix("/") { + return short + } + return URL(fileURLWithPath: path).lastPathComponent } private func relative(_ path: String, _ root: String) -> String {