Skip to content
4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
B900001AA1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000019A1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift */; };
B900001BA1B2C3D4E5F60719 /* CloseWorkspacesConfirmDialogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B900001CA1B2C3D4E5F60719 /* CloseWorkspacesConfirmDialogUITests.swift */; };
B9000023A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000022A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift */; };
B9000130A1B2C3D4E5F60719 /* SidebarPullRequestInteractivityUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000131A1B2C3D4E5F60719 /* SidebarPullRequestInteractivityUITests.swift */; };
F0F0A001A1B2C3D4E5F60718 /* WorkspaceSidebarScrollUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F0A002A1B2C3D4E5F60718 /* WorkspaceSidebarScrollUITests.swift */; };
B9000024A1B2C3D4E5F60719 /* Sentry-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = A5001253 /* Sentry-Dynamic */; };
B9000025A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000026A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift */; };
Expand Down Expand Up @@ -597,6 +598,7 @@
B900001CA1B2C3D4E5F60719 /* CloseWorkspacesConfirmDialogUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseWorkspacesConfirmDialogUITests.swift; sourceTree = "<group>"; };
B9000022A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseWorkspaceCmdDUITests.swift; sourceTree = "<group>"; };
B9000026A1B2C3D4E5F60719 /* CloseWindowConfirmDialogUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseWindowConfirmDialogUITests.swift; sourceTree = "<group>"; };
B9000131A1B2C3D4E5F60719 /* SidebarPullRequestInteractivityUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarPullRequestInteractivityUITests.swift; sourceTree = "<group>"; };
BC39DE4B96D1931C52AF7D68 /* SidebarOrderingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarOrderingTests.swift; sourceTree = "<group>"; };
BEE83F8394D90ACACD8E19DD /* WindowAndDragTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAndDragTests.swift; sourceTree = "<group>"; };
2907A0042907A0042907A004 /* AppDelegateIssue2907RoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateIssue2907RoutingTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -762,6 +764,7 @@
B8F266276A1A3D9A45BD840F /* DisplayResolutionRegressionUITests.swift */,
F0F0A002A1B2C3D4E5F60718 /* WorkspaceSidebarScrollUITests.swift */,
C2577001A1B2C3D4E5F60718 /* TerminalCmdClickUITests.swift */,
B9000131A1B2C3D4E5F60719 /* SidebarPullRequestInteractivityUITests.swift */,
E6FA9085A1B2C3D4E5F60718 /* WorkspaceDescriptionUITests.swift */,
D0E0F0B1A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift */,
D0E0F0B5A1B2C3D4E5F60718 /* FindSelectionShortcutUITests.swift */,
Expand Down Expand Up @@ -1517,6 +1520,7 @@
C0DE32470000000000000005 /* CommandPaletteIdentifierClipboardUITests.swift in Sources */,
B8F266266A1A3D9A45BD840F /* DisplayResolutionRegressionUITests.swift in Sources */,
C2577000A1B2C3D4E5F60718 /* TerminalCmdClickUITests.swift in Sources */,
B9000130A1B2C3D4E5F60719 /* SidebarPullRequestInteractivityUITests.swift in Sources */,
E6FA9084A1B2C3D4E5F60718 /* WorkspaceDescriptionUITests.swift in Sources */,
D0E0F0B0A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift in Sources */,
D0E0F0B4A1B2C3D4E5F60718 /* FindSelectionShortcutUITests.swift in Sources */,
Expand Down
123 changes: 104 additions & 19 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -53071,6 +53071,40 @@
}
}
},
"settings.app.openSidebarPRLinks.subtitleDisabled": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Enable sidebar PR clickability to choose where PR links open."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "PRリンクの開き先を選ぶには、サイドバーのPRクリックを有効にしてください。"
}
}
}
},
"settings.app.openSidebarPRLinks.subtitleHidden": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Enable sidebar PR visibility to choose where PR links open."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "PRリンクの開き先を選ぶには、サイドバーのPR表示を有効にしてください。"
}
}
}
},
"settings.app.openSidebarPortLinks": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -55836,115 +55870,166 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Display review items (PR/MR/etc.) with status, number, and clickable link."
"value": "Display review items (PR/MR/etc.) with status and number."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ステータス、番号、クリック可能なリンク付きのレビュー項目(PR/MRなど)を表示します。"
"value": "ステータスと番号付きのレビュー項目(PR/MRなど)を表示します。"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "显示包含状态、编号和可点击链接的审查项(PR/MR 等)。"
"value": "显示包含状态和编号的审查项(PR/MR 等)。"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "顯示審查項目(PR/MR 等),包含狀態、編號和可點擊的連結。"
"value": "顯示審查項目(PR/MR 等),包含狀態和編號。"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "상태, 번호 및 클릭 가능한 링크가 있는 리뷰 항목(PR/MR 등)을 표시합니다."
"value": "상태와 번호가 있는 리뷰 항목(PR/MR 등)을 표시합니다."
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Review-Elemente (PR/MR/etc.) mit Status, Nummer und anklickbarem Link anzeigen."
"value": "Review-Elemente (PR/MR/etc.) mit Status und Nummer anzeigen."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Mostrar elementos de revisión (PR/MR/etc.) con estado, número y enlace interactivo."
"value": "Mostrar elementos de revisión (PR/MR/etc.) con estado y número."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Afficher les éléments de revue (PR/MR/etc.) avec le statut, le numéro et un lien cliquable."
"value": "Afficher les éléments de revue (PR/MR/etc.) avec le statut et le numéro."
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Visualizza elementi di revisione (PR/MR/ecc.) con stato, numero e link cliccabile."
"value": "Visualizza elementi di revisione (PR/MR/ecc.) con stato e numero."
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Vis gennemgangselementer (PR/MR osv.) med status, nummer og klikbart link."
"value": "Vis gennemgangselementer (PR/MR osv.) med status og nummer."
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Wyświetlaj elementy przeglądu (PR/MR/itp.) ze statusem, numerem i klikalnym linkiem."
"value": "Wyświetlaj elementy przeglądu (PR/MR/itp.) ze statusem i numerem."
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Отображать элементы проверки (PR/MR и т.д.) со статусом, номером и ссылкой."
"value": "Отображать элементы проверки (PR/MR и т.д.) со статусом и номером."
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Prikaži stavke za pregled (PR/MR/itd.) sa statusom, brojem i linkom."
"value": "Prikaži stavke za pregled (PR/MR/itd.) sa statusom i brojem."
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "عرض عناصر المراجعة (طلبات السحب/الدمج/إلخ.) مع الحالة والرقم والرابط القابل للنقر."
"value": "عرض عناصر المراجعة (طلبات السحب/الدمج/إلخ.) مع الحالة والرقم."
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Vis gjennomgangselementer (PR/MR/osv.) med status, nummer og klikkbar lenke."
"value": "Vis gjennomgangselementer (PR/MR/osv.) med status og nummer."
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Exibir itens de revisão (PR/MR/etc.) com status, número e link clicável."
"value": "Exibir itens de revisão (PR/MR/etc.) com status e número."
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "แสดงรายการตรวจสอบ (PR/MR/อื่นๆ) พร้อมสถานะ หมายเลข และลิงก์ที่คลิกได้"
"value": "แสดงรายการตรวจสอบ (PR/MR/อื่นๆ) พร้อมสถานะและหมายเลข"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Durum, numara ve tıklanabilir bağlantıyla inceleme öğelerini (PR/MR/vb.) göster."
"value": "Durum ve numarayla inceleme öğelerini (PR/MR/vb.) göster."
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Відображати елементи перегляду (PR/MR тощо) зі статусом, номером та клікабельним посиланням."
"value": "Відображати елементи перегляду (PR/MR тощо) зі статусом і номером."
}
}
}
},
"settings.app.showPullRequests.subtitle.basic": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Display review items (PR/MR/etc.) with status and number."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ステータスと番号付きのレビュー項目(PR/MRなど)を表示します。"
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused localization key duplicates existing subtitle string

Low Severity

The new localization key "settings.app.showPullRequests.subtitle.basic" is added to Localizable.xcstrings but is never referenced anywhere in the Swift source code. Its English and Japanese values are identical to the already-used "settings.app.showPullRequests.subtitle" key. This is dead localization data that will confuse future contributors about whether the .basic variant is consumed elsewhere.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f5dffb2. Configure here.

Comment on lines +55986 to +56001

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

settings.app.showPullRequests.subtitle.basic has identical content to the updated settings.app.showPullRequests.subtitle — clarify intent.

Both keys now share the same EN value ("Display review items (PR/MR/etc.) with status and number.") and the same JA value. Two outcomes are possible:

  1. Distinct UI states — if .basic is rendered when clickability is off and the parent key is rendered when clickability is on, the identical text gives users no indication that anything differs between the two states. The .basic subtitle should carry different copy that reflects non-interactive display (e.g., "Review items are shown as plain, non-clickable text.").
  2. Same UI state — if only one of the two keys is actually used in the final render path, the other is dead string content. The used key should be kept and the other removed to avoid translator drift (future updates to one key may not be reflected in the other).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/Localizable.xcstrings` around lines 55986 - 56001, The two
localization keys settings.app.showPullRequests.subtitle.basic and
settings.app.showPullRequests.subtitle currently contain identical EN and JA
strings; decide which behavior you intend and update accordingly: if they
represent distinct UI states (non-clickable vs clickable), change
settings.app.showPullRequests.subtitle.basic to copy that communicates the
non-interactive state (e.g., “Review items are shown as plain, non-clickable
text.”) in both languages; otherwise remove the unused key so only the active
key (settings.app.showPullRequests.subtitle) remains to avoid translator drift,
and ensure any UI code referencing either key (search for references to
settings.app.showPullRequests.subtitle.basic and
settings.app.showPullRequests.subtitle) is updated to use the chosen key.

},
"settings.app.makeSidebarPullRequestClickable": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Make Sidebar PR Clickable"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "サイドバーのPRをクリック可能にする"
}
}
}
},
"settings.app.makeSidebarPullRequestClickable.subtitle": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Off by default. Review items stay visible as plain text, and clicks in that area select the workspace row."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "デフォルトではオフです。レビュー項目は通常のテキストとして表示され、その領域をクリックするとワークスペース行が選択されます。"
}
}
}
Expand Down
40 changes: 19 additions & 21 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9633,6 +9633,7 @@ private struct SidebarTabItemSettingsSnapshot: Equatable {
let usesVerticalBranchLayout: Bool
let showsGitBranchIcon: Bool
let showsSSH: Bool
let makesPullRequestsClickable: Bool
let openPullRequestLinksInCmuxBrowser: Bool
let openPortLinksInCmuxBrowser: Bool
let showsNotificationMessage: Bool
Expand Down Expand Up @@ -9662,6 +9663,7 @@ private struct SidebarTabItemSettingsSnapshot: Equatable {
usesVerticalBranchLayout = SidebarBranchLayoutSettings.usesVerticalLayout(defaults: defaults)
showsGitBranchIcon = Self.bool(defaults: defaults, key: "sidebarShowGitBranchIcon", defaultValue: false)
showsSSH = Self.bool(defaults: defaults, key: "sidebarShowSSH", defaultValue: true)
makesPullRequestsClickable = SidebarPullRequestClickabilitySettings.isClickable(defaults: defaults)
openPullRequestLinksInCmuxBrowser = BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowser(
defaults: defaults
)
Expand Down Expand Up @@ -13164,28 +13166,24 @@ private struct TabItemView: View, Equatable {
VStack(alignment: .leading, spacing: 1) {
ForEach(workspaceSnapshot.pullRequestRows) { pullRequest in
let pullRequestNumber = String(pullRequest.number)
Button(action: {
openPullRequestLink(pullRequest.url)
}) {
HStack(spacing: 4) {
PullRequestStatusIcon(
status: pullRequest.status,
color: pullRequestForegroundColor
)
Text("\(pullRequest.label) #\(pullRequestNumber)")
.underline()
.lineLimit(1)
.truncationMode(.tail)
Text(pullRequestStatusLabel(pullRequest.status))
.lineLimit(1)
Spacer(minLength: 0)
}
.font(.system(size: 10, weight: .semibold))
.foregroundColor(pullRequestForegroundColor)
.opacity(pullRequest.isStale ? 0.5 : 1)
let pullRequestTitle = "\(pullRequest.label) #\(pullRequestNumber)"
let rowContent = HStack(spacing: 4) {
PullRequestStatusIcon(status: pullRequest.status, color: pullRequestForegroundColor)
Text(pullRequestTitle).underline(settings.makesPullRequestsClickable).lineLimit(1).truncationMode(.tail)
Text(pullRequestStatusLabel(pullRequest.status)).lineLimit(1)
Spacer(minLength: 0)
}
.font(.system(size: 10, weight: .semibold))
.foregroundColor(pullRequestForegroundColor)
.opacity(pullRequest.isStale ? 0.5 : 1)
if settings.makesPullRequestsClickable {
Button(action: { openPullRequestLink(pullRequest.url) }) { rowContent }
.buttonStyle(.plain)
.safeHelp(String(localized: "sidebar.pullRequest.openTooltip", defaultValue: "Open \(pullRequestTitle)"))
.accessibilityIdentifier("SidebarPullRequestRow")
} else {
rowContent.accessibilityElement(children: .combine).accessibilityIdentifier("SidebarPullRequestRow")
}
.buttonStyle(.plain)
.safeHelp(String(localized: "sidebar.pullRequest.openTooltip", defaultValue: "Open \(pullRequest.label) #\(pullRequestNumber)"))
}
}
}
Expand Down
11 changes: 5 additions & 6 deletions Sources/KeyboardShortcutSettingsFileStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ final class CmuxSettingsFileStore {
"sidebar.showNotificationMessage",
"sidebar.showBranchDirectory",
"sidebar.showPullRequests",
"sidebar.makePullRequestsClickable",
"sidebar.openPullRequestLinksInCmuxBrowser",
"sidebar.openPortLinksInCmuxBrowser",
"sidebar.showSSH",
Expand Down Expand Up @@ -526,12 +527,9 @@ final class CmuxSettingsFileStore {
if let value = jsonBool(section["showNotificationMessage"]) {
snapshot.managedUserDefaults[SidebarWorkspaceDetailSettings.showNotificationMessageKey] = .bool(value)
}
if let value = jsonBool(section["showBranchDirectory"]) {
snapshot.managedUserDefaults["sidebarShowBranchDirectory"] = .bool(value)
}
if let value = jsonBool(section["showPullRequests"]) {
snapshot.managedUserDefaults["sidebarShowPullRequest"] = .bool(value)
}
if let value = jsonBool(section["showBranchDirectory"]) { snapshot.managedUserDefaults["sidebarShowBranchDirectory"] = .bool(value) }
if let value = jsonBool(section["showPullRequests"]) { snapshot.managedUserDefaults["sidebarShowPullRequest"] = .bool(value) }
if let value = jsonBool(section["makePullRequestsClickable"]) { snapshot.managedUserDefaults[SidebarPullRequestClickabilitySettings.key] = .bool(value) }
if let value = jsonBool(section["openPullRequestLinksInCmuxBrowser"]) {
snapshot.managedUserDefaults[BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowserKey] = .bool(value)
}
Expand Down Expand Up @@ -1276,6 +1274,7 @@ final class CmuxSettingsFileStore {
"showNotificationMessage": SidebarWorkspaceDetailSettings.defaultShowNotificationMessage,
"showBranchDirectory": true,
"showPullRequests": true,
"makePullRequestsClickable": SidebarPullRequestClickabilitySettings.defaultClickable,
"openPullRequestLinksInCmuxBrowser": BrowserLinkOpenSettings.defaultOpenSidebarPullRequestLinksInCmuxBrowser,
"openPortLinksInCmuxBrowser": BrowserLinkOpenSettings.defaultOpenSidebarPortLinksInCmuxBrowser,
"showSSH": true,
Expand Down
12 changes: 12 additions & 0 deletions Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ enum SidebarWorkspaceDetailSettings {
}
}

enum SidebarPullRequestClickabilitySettings {
static let key = "sidebarMakePullRequestClickable"
static let defaultClickable = false

static func isClickable(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: key) == nil {
return defaultClickable
}
return defaults.bool(forKey: key)
}
}

struct SidebarWorkspaceAuxiliaryDetailVisibility: Equatable {
let showsMetadata: Bool
let showsLog: Bool
Expand Down
Loading