Fix ⌘F focus, hung-turn Stop, narrow tabs and other QA findings

This commit is contained in:
Ian Gulliver
2026-10-23 15:34:01 -07:00
parent ff8d4fb4be
commit 382d27d356
9 changed files with 78 additions and 65 deletions
+2 -2
View File
@@ -652,8 +652,8 @@ private struct DiffLineRow: View {
}
.padding(.horizontal, 10)
.frame(maxWidth: .infinity, alignment: .leading)
.background(row.kind.background)
.background(hover ? Palette.accent.opacity(0.08) : .clear)
.background(row.kind.background, in: Rectangle())
.background(hover ? Palette.accent.opacity(0.08) : .clear, in: Rectangle())
.overlay(alignment: .leading) {
if mark {
Rectangle().fill(Palette.accent).frame(width: 3)
+4
View File
@@ -101,6 +101,10 @@ struct FilterField: View {
var body: some View {
TextField(placeholder, text: $text)
.modifier(FieldStyle(focused: focused))
#if os(iOS)
.textInputAutocapitalization(.never)
#endif
.autocorrectionDisabled()
.focused($focused)
.onChange(of: focusSignal, initial: true) { old, new in
if new != 0 && (old != new || !focused) {
+44 -53
View File
@@ -117,29 +117,9 @@ private struct LeftPane: View {
let tab = overlays.leftTab
VStack(spacing: 0) {
HStack(spacing: 4) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 4) {
PaneTabButton(label: "Lead", active: tab == .lead) {
pick(.lead)
}
PaneTabButton(label: "Status", active: tab == .status) {
pick(.status)
}
PaneTabButton(label: "Notes", active: tab == .notes) {
pick(.notes)
}
PaneTabButton(label: "Workflows", active: tab == .workflows) {
pick(.workflows)
}
PaneTabButton(label: "Server", active: tab == .server) {
pick(.server)
}
PaneTabButton(label: "Settings", active: tab == .settings) {
pick(.settings)
}
}
FoldingTabs(tabs: [(.lead, "Lead"), (.status, "Status"), (.notes, "Notes"), (.workflows, "Workflows"), (.server, "Server"), (.settings, "Settings")], active: tab) { t in
pick(t)
}
.scrollBounceBehavior(.basedOnSize, axes: .horizontal)
HStack(spacing: 10) {
if !store.connected {
Text("reconnecting…")
@@ -411,12 +391,6 @@ private struct PreviewLink: View {
struct TabStrip: View {
@Environment(Store.self) private var store
@Environment(Overlays.self) private var overlays
@Environment(\.paneAccent) private var accent
@ViewState private var widths: [TaskTab: CGFloat] = [:]
@ViewState private var moreWidth: CGFloat = 0
@ViewState private var stripWidth: CGFloat = 0
@ViewState private var moreFrame: CGRect = .zero
private struct Info {
var order: [TaskTab]
@@ -448,19 +422,41 @@ struct TabStrip: View {
return Info(order: order, tab: tab)
}
private func kept(_ info: Info) -> [TaskTab] {
let gap: CGFloat = 4
let inner = stripWidth - 24
func total(_ ts: [TaskTab]) -> CGFloat {
ts.reduce(0) { $0 + (widths[$1] ?? 0) + gap }
var body: some View {
let info = info()
FoldingTabs(tabs: info.order.map { ($0, tabLabels[$0]!) }, active: info.tab) { t in
store.openTab(t)
}
let all = info.order
if stripWidth == 0 || total(all) <= inner {
.padding(.horizontal, 12)
.overlay(alignment: .bottom) {
Rectangle().fill(Palette.border).frame(height: 1)
}
}
}
struct FoldingTabs<ID: Hashable>: View {
let tabs: [(id: ID, label: String)]
let active: ID
let pick: (ID) -> Void
@Environment(Overlays.self) private var overlays
@Environment(\.paneAccent) private var accent
@ViewState private var widths: [ID: CGFloat] = [:]
@ViewState private var moreWidth: CGFloat = 0
@ViewState private var stripWidth: CGFloat = 0
@ViewState private var moreFrame: CGRect = .zero
private func kept() -> [ID] {
let gap: CGFloat = 4
func total(_ ids: [ID]) -> CGFloat {
ids.reduce(0) { $0 + (widths[$1] ?? 0) + gap }
}
let all = tabs.map(\.id)
if stripWidth == 0 || total(all) <= stripWidth {
return all
}
let budget = inner - moreWidth - gap
var kept: [TaskTab] = []
for t in [info.tab] + all.filter({ $0 != info.tab }) {
let budget = stripWidth - moreWidth - gap
var kept: [ID] = []
for t in [active] + all.filter({ $0 != active }) {
if !kept.isEmpty && total(kept + [t]) > budget {
break
}
@@ -470,20 +466,19 @@ struct TabStrip: View {
}
var body: some View {
let info = info()
let kept = kept(info)
let hidden = info.order.filter { !kept.contains($0) }
let kept = kept()
let hidden = tabs.filter { !kept.contains($0.id) }
HStack(spacing: 4) {
ForEach(info.order.filter { kept.contains($0) }, id: \.self) { t in
PaneTabButton(label: tabLabels[t]!, active: t == info.tab) {
store.openTab(t)
ForEach(tabs.filter { kept.contains($0.id) }, id: \.id) { t in
PaneTabButton(label: t.label, active: t.id == active) {
pick(t.id)
}
}
if !hidden.isEmpty {
MoreButton {
overlays.menu = OverlayMenu(anchor: moreFrame, accent: accent, entries: hidden.map { t in
MenuEntry(id: t.rawValue, label: tabLabels[t]!) {
store.openTab(t)
MenuEntry(id: t.label, label: t.label) {
pick(t.id)
}
})
}
@@ -495,17 +490,16 @@ struct TabStrip: View {
}
Spacer(minLength: 0)
}
.padding(.horizontal, 12)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.clipped()
.background {
HStack(spacing: 4) {
ForEach(info.order, id: \.self) { t in
PaneTabButton(label: tabLabels[t]!, active: t == info.tab) {}
ForEach(tabs, id: \.id) { t in
PaneTabButton(label: t.label, active: t.id == active) {}
.onGeometryChange(for: CGFloat.self) { g in
g.size.width
} action: { w in
widths[t] = w
widths[t.id] = w
}
}
MoreButton {}
@@ -523,9 +517,6 @@ struct TabStrip: View {
} action: { w in
stripWidth = w
}
.overlay(alignment: .bottom) {
Rectangle().fill(Palette.border).frame(height: 1)
}
}
}
+1 -1
View File
@@ -137,7 +137,7 @@ private struct ServerColorPicker: View {
var body: some View {
let origin = store.api.origin.absoluteString
let current = Servers.shared.record(origin)?.color
HStack(spacing: 4) {
FlowLayout(spacing: 4) {
ForEach(taskPalette.indices, id: \.self) { i in
Button {
Servers.shared.setColor(origin, i)
+5
View File
@@ -99,6 +99,11 @@ final class Servers {
connections[origin] = nil
Task {
await connection.stop()
let scope = draftOrigin(parseOrigin(origin)!) + "."
for record in fetchRecords(FetchDescriptor<DraftRecord>()) where record.scope.hasPrefix(scope) {
persistence.delete(record)
}
saveRecords()
}
}
}
+1 -3
View File
@@ -46,9 +46,7 @@ func runGo(_ key: String, _ store: Store, _ overlays: Overlays) {
}
case "f":
overlays.switcher?.close()
if store.paneFocus.inTaskPane {
overlays.filterTodo += 1
}
overlays.filterTodo += 1
case "/":
overlays.showAlert(ShortcutsHelp())
default: