Skip to content

feat(sidebar): make listening ports clickable to open in browser - #1844

Merged
lawrencecchen merged 2 commits into
manaflow-ai:mainfrom
mvanhorn:osc/1602-sidebar-ports-clickable
Mar 26, 2026
Merged

lawrencecchen merged 2 commits into
manaflow-ai:mainfrom
mvanhorn:osc/1602-sidebar-ports-clickable

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Mar 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Ports displayed in the sidebar are now clickable buttons that open http://localhost:{port} in the cmux built-in browser pane
  • Follows the same pattern as the existing PR link buttons directly above in the sidebar (ContentView.swift:11046-11068)
  • Falls back to system default browser if the cmux browser pane cannot be opened

Testing

  • Verified the change compiles against the existing code patterns
  • The openPortLink function mirrors openPullRequestLink at ContentView.swift:11768 - same tabManager.openBrowser call with preferSplitRight: true
  • Port buttons use .buttonStyle(.plain) and .safeHelp() for accessibility, matching PR link buttons
  • Tooltip string is localized with String(localized: "sidebar.port.openTooltip", defaultValue: "Open localhost:\(port)")
  • No typing-latency-sensitive paths affected (port row is outside TabItemView's .equatable() scope)

Demo Video

  • Video URL or attachment: (unable to record - no local cmux build environment; requires Xcode + GhosttyKit setup)

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

This contribution was developed with AI assistance (Claude Code).

Fixes #1602


Summary by cubic

Make sidebar listening ports clickable. Clicking opens http://localhost:{port} in the built-in browser, with a system browser fallback.

  • New Features
    • Plain, underlined port buttons with tooltip; open via tabManager.openBrowser(preferSplitRight: true) or NSWorkspace.shared.open; respects the openSidebarPullRequestLinksInCmuxBrowser preference. Keeps monospaced style, color, and single-line layout.
    • Localized label and tooltip via String(localized:) using sidebar.port.label and sidebar.port.openTooltip (English, Japanese).

Written for commit 5b20bf0. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Ports now display as individual clickable buttons for direct browser access to local services.
    • Each port button includes a tooltip describing the action and port.
  • Documentation

    • Added localized labels and tooltip strings for port UI (English and Japanese).

Wrap each sidebar port in a Button that opens http://localhost:{port}
in the cmux built-in browser (or system browser as fallback), matching
the existing PR link click behavior.

Fixes manaflow-ai#1602

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 20, 2026

Copy link
Copy Markdown

@mvanhorn is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Mar 20, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Ports in the sidebar are now rendered as individual clickable buttons. Clicking a port calls a new handler that constructs http://localhost:<port>, updates selection, and attempts to open the URL via tabManager.openBrowser(...) with NSWorkspace.shared.open(...) as a fallback. Two localization keys were added.

Changes

Cohort / File(s) Summary
Sidebar UI & behavior
Sources/ContentView.swift
Replaced single concatenated ports Text with an HStack of per-port Buttons (ForEach(tab.listeningPorts, id: \.self)). Added openPortLink(_:) to build http://localhost:<port>, call updateSelection(), and open the URL using tabManager.openBrowser(...) or fallback to NSWorkspace.shared.open(...). Each button includes a .safeHelp(...) tooltip.
Localizations
Resources/Localizable.xcstrings
Added sidebar.port.label and sidebar.port.openTooltip keys with en and ja entries for port formatting and per-port open tooltip text.

Sequence Diagram

sequenceDiagram
    actor User
    participant ContentView as ContentView<br/>(Port Button)
    participant openPortLink as openPortLink<br/>Function
    participant TabManager as tabManager
    participant NSWorkspace as NSWorkspace

    User->>ContentView: Click port button
    ContentView->>openPortLink: openPortLink(port)
    openPortLink->>openPortLink: Construct http://localhost:port
    openPortLink->>ContentView: updateSelection()
    openPortLink->>TabManager: openBrowser(url)
    alt Browser opened successfully
        TabManager-->>openPortLink: returns non-nil
    else Browser method fails
        TabManager-->>openPortLink: returns nil
        openPortLink->>NSWorkspace: open(url)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I tap a port, a soft little spring,
localhost hops and the browser will sing,
Buttons aligned in a tidy row,
Click, and away the small windows go —
A rabbit's quick hop to where servers flow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main feature: making ports clickable to open in browser.
Description check ✅ Passed Description covers the summary and testing sections adequately, though no demo video was provided due to environment constraints.
Linked Issues check ✅ Passed Code changes fully implement the requirement from #1602: ports are now clickable buttons opening localhost URLs in browser with system fallback.
Out of Scope Changes check ✅ Passed All changes are scoped to making ports clickable in sidebar; localization keys and port button implementation directly support the #1602 objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 1 file

@greptile-apps

greptile-apps Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes listening-port entries in the cmux sidebar interactive: each port number becomes a clickable button that opens http://localhost:{port} in the cmux built-in browser pane (with a system-browser fallback). The structural approach — ForEach over ports, .buttonStyle(.plain), .safeHelp() tooltip — faithfully mirrors the existing pull-request link buttons introduced above in TabItemView.

Two issues need attention before merging:

  • Missing Localizable.xcstrings entry – The new tooltip key sidebar.port.openTooltip is never added to Resources/Localizable.xcstrings, violating the project's mandatory localization policy. The equivalent PR-link key ships with 16 language translations; port tooltips will always fall back to the English defaultValue for all non-English users.
  • User preference not respected in openPortLink – Unlike openPullRequestLink, which gates in-app browser behavior on the openSidebarPullRequestLinksInCmuxBrowser preference, openPortLink unconditionally tries the cmux browser first. Users who have configured links to open in the system browser will get inconsistent behavior between PR links and port links.

Confidence Score: 3/5

  • The PR is functional but has two issues — a missing localization key and an inconsistent browser-preference behavior — that should be fixed before merging.
  • The UI change itself is small, well-patterned, and correctly falls back to the system browser. However, the missing Localizable.xcstrings entry violates an explicitly documented project policy, and the openPortLink helper diverges from the reference openPullRequestLink in a user-visible way (ignoring the in-app-browser preference). Neither issue causes a crash, but both produce observable defects in non-English locales and for users who prefer the system browser.
  • Sources/ContentView.swift (openPortLink preference gap) and Resources/Localizable.xcstrings (missing sidebar.port.openTooltip key)

Important Files Changed

Filename Overview
Sources/ContentView.swift Adds clickable port buttons to the sidebar that open http://localhost:{port} in the cmux browser pane. The UI pattern closely follows the existing PR-link buttons, but the new openPortLink helper bypasses the openSidebarPullRequestLinksInCmuxBrowser user preference, and the new localization key sidebar.port.openTooltip is missing from Localizable.xcstrings.

Sequence Diagram

sequenceDiagram
    participant User
    participant SidebarPortButton
    participant openPortLink
    participant tabManager
    participant NSWorkspace

    User->>SidebarPortButton: Click `:8080`
    SidebarPortButton->>openPortLink: openPortLink(8080)
    openPortLink->>openPortLink: guard URL(string: "http://localhost:8080")
    openPortLink->>openPortLink: updateSelection()
    openPortLink->>tabManager: openBrowser(inWorkspace:, url:, preferSplitRight: true, insertAtEnd: true)
    alt Browser pane opened successfully
        tabManager-->>openPortLink: returns non-nil
        Note over openPortLink: Done — URL shown in cmux pane
    else Browser pane unavailable
        tabManager-->>openPortLink: returns nil
        openPortLink->>NSWorkspace: open(url)
        NSWorkspace-->>User: Opens in system browser
    end
Loading

Last reviewed commit: "feat(sidebar): make ..."

Comment thread Sources/ContentView.swift
.underline()
}
.buttonStyle(.plain)
.safeHelp(String(localized: "sidebar.port.openTooltip", defaultValue: "Open localhost:\(port)"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Localization key missing from Localizable.xcstrings

The new key sidebar.port.openTooltip is used here but was never added to Resources/Localizable.xcstrings. The CLAUDE.md style guide is explicit: "All user-facing strings must be localized. Keys go in Resources/Localizable.xcstrings with translations for all supported languages."

The existing parallel key sidebar.pullRequest.openTooltip has entries for 16 languages (en, ja, zh-Hans, zh-Hant, ko, de, es, fr, it, da, pl, ru, bs, ar, nb, pt-BR, th, tr). Without the new key, non-English users will always see the defaultValue fallback string, and the string will never be picked up by translators.

Context Used: CLAUDE.md (source)

Comment thread Sources/ContentView.swift
Comment on lines +11795 to +11806
private func openPortLink(_ port: Int) {
guard let url = URL(string: "http://localhost:\(port)") else { return }
updateSelection()
if tabManager.openBrowser(
inWorkspace: tab.id,
url: url,
preferSplitRight: true,
insertAtEnd: true
) == nil {
NSWorkspace.shared.open(url)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 User preference for in-app browser not respected

openPortLink unconditionally attempts to open URLs in the cmux browser pane, while the analogous openPullRequestLink (which this function mirrors) gates that behavior behind the openSidebarPullRequestLinksInCmuxBrowser user preference:

// openPullRequestLink checks the preference first
if openSidebarPullRequestLinksInCmuxBrowser {
    if tabManager.openBrowser(...) == nil {
        NSWorkspace.shared.open(url)
    }
    return
}
NSWorkspace.shared.open(url)

A user who has toggled the setting to always use the system browser (i.e., openSidebarPullRequestLinksInCmuxBrowser = false) will find that PR links open in the system browser, while clicking a port number opens in the cmux browser pane — an inconsistent and surprising experience. The fix would either be to read the same preference, or to introduce a dedicated openSidebarPortLinksInCmuxBrowser preference with its own toggle in Settings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Line 11079: Replace the bare Text literal that displays the port (currently
Text(":\(port)") in ContentView) with a localized string using
String(localized:..., defaultValue:...) so the user-facing port label is
localizable; update the Text initializer to pass the localized string
constructed with the port value (referencing the existing port variable) and add
the localization key (e.g., "port.label") with appropriate English and Japanese
entries in Resources/Localizable.xcstrings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fac4755-d02a-418d-bfeb-6bb379da2f84

📥 Commits

Reviewing files that changed from the base of the PR and between ed18e32 and 4c4faa6.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

Comment thread Sources/ContentView.swift Outdated
- Localize port label text with String(localized:) instead of bare literal
- Add sidebar.port.label and sidebar.port.openTooltip keys to
  Localizable.xcstrings with English and Japanese translations
- Respect openSidebarPullRequestLinksInCmuxBrowser user preference in
  openPortLink, matching the openPullRequestLink pattern exactly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:11798">
P2: Port links are incorrectly gated by a PR-link-only preference, causing port clicks to skip in-app opening when that unrelated setting is off.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/ContentView.swift
Comment on lines +11798 to +11809
if openSidebarPullRequestLinksInCmuxBrowser {
if tabManager.openBrowser(
inWorkspace: tab.id,
url: url,
preferSplitRight: true,
insertAtEnd: true
) == nil {
NSWorkspace.shared.open(url)
}
return
}
NSWorkspace.shared.open(url)

@cubic-dev-ai cubic-dev-ai Bot Mar 20, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Port links are incorrectly gated by a PR-link-only preference, causing port clicks to skip in-app opening when that unrelated setting is off.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/ContentView.swift, line 11798:

<comment>Port links are incorrectly gated by a PR-link-only preference, causing port clicks to skip in-app opening when that unrelated setting is off.</comment>

<file context>
@@ -11795,14 +11795,18 @@ private struct TabItemView: View, Equatable {
-            insertAtEnd: true
-        ) == nil {
-            NSWorkspace.shared.open(url)
+        if openSidebarPullRequestLinksInCmuxBrowser {
+            if tabManager.openBrowser(
+                inWorkspace: tab.id,
</file context>
Suggested change
if openSidebarPullRequestLinksInCmuxBrowser {
if tabManager.openBrowser(
inWorkspace: tab.id,
url: url,
preferSplitRight: true,
insertAtEnd: true
) == nil {
NSWorkspace.shared.open(url)
}
return
}
NSWorkspace.shared.open(url)
if tabManager.openBrowser(
inWorkspace: tab.id,
url: url,
preferSplitRight: true,
insertAtEnd: true
) == nil {
NSWorkspace.shared.open(url)
}
Fix with Cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
Sources/ContentView.swift (1)

11798-11809: Consider using a dedicated setting key for port-link open behavior.

openPortLink currently reuses openSidebarPullRequestLinksInCmuxBrowser. A port-specific key (with fallback migration from the current key) would avoid semantic coupling and future settings confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 11798 - 11809, The code currently
uses the openSidebarPullRequestLinksInCmuxBrowser boolean to control port link
opening, creating semantic coupling; define a new dedicated setting key (e.g.,
openPortLink or similar) and use it in this block instead of
openSidebarPullRequestLinksInCmuxBrowser, while preserving backward
compatibility by migrating or falling back to the old key when the new key is
unset. Update the logic that checks openSidebarPullRequestLinksInCmuxBrowser to
read the new setting (and if nil/absent, read the old setting and optionally
write it to the new key), and ensure tabManager.openBrowser(...) and
NSWorkspace.shared.open(url) behavior remains identical when driven by the new
key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/ContentView.swift`:
- Around line 11798-11809: The code currently uses the
openSidebarPullRequestLinksInCmuxBrowser boolean to control port link opening,
creating semantic coupling; define a new dedicated setting key (e.g.,
openPortLink or similar) and use it in this block instead of
openSidebarPullRequestLinksInCmuxBrowser, while preserving backward
compatibility by migrating or falling back to the old key when the new key is
unset. Update the logic that checks openSidebarPullRequestLinksInCmuxBrowser to
read the new setting (and if nil/absent, read the old setting and optionally
write it to the new key), and ensure tabManager.openBrowser(...) and
NSWorkspace.shared.open(url) behavior remains identical when driven by the new
key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53e711ac-8169-4754-828c-bd7e5c47f46a

📥 Commits

Reviewing files that changed from the base of the PR and between 4c4faa6 and 5b20bf0.

📒 Files selected for processing (2)
  • Resources/Localizable.xcstrings
  • Sources/ContentView.swift
✅ Files skipped from review due to trivial changes (1)
  • Resources/Localizable.xcstrings

@lawrencecchen
lawrencecchen merged commit 819ceb8 into manaflow-ai:main Mar 26, 2026
3 of 4 checks passed
@lawrencecchen

Copy link
Copy Markdown
Contributor

Thank you for the contribution!

lawrencecchen added a commit that referenced this pull request Mar 26, 2026
Port links were reusing the PR-link preference
(openSidebarPullRequestLinksInCmuxBrowser), causing inconsistent
behavior when users toggled that setting. Adds a dedicated
openSidebarPortLinksInCmuxBrowser setting with its own toggle in
Settings so port and PR link behavior can be controlled independently.

Addresses review feedback from #1844
lawrencecchen added a commit that referenced this pull request Mar 27, 2026
…2219)

Port links were reusing the PR-link preference
(openSidebarPullRequestLinksInCmuxBrowser), causing inconsistent
behavior when users toggled that setting. Adds a dedicated
openSidebarPortLinksInCmuxBrowser setting with its own toggle in
Settings so port and PR link behavior can be controlled independently.

Addresses review feedback from #1844

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Thanks for the merge! Happy to contribute to cmux.

bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…aflow-ai#1844)

* feat(sidebar): make listening ports clickable to open in browser

Wrap each sidebar port in a Button that opens http://localhost:{port}
in the cmux built-in browser (or system browser as fallback), matching
the existing PR link click behavior.

Fixes manaflow-ai#1602

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bot review feedback on port clickability

- Localize port label text with String(localized:) instead of bare literal
- Add sidebar.port.label and sidebar.port.openTooltip keys to
  Localizable.xcstrings with English and Japanese translations
- Respect openSidebarPullRequestLinksInCmuxBrowser user preference in
  openPortLink, matching the openPullRequestLink pattern exactly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
israeligal pushed a commit to israeligal/cmux that referenced this pull request May 2, 2026
Port links were reusing the PR-link preference
(openSidebarPullRequestLinksInCmuxBrowser), causing inconsistent
behavior when users toggled that setting. Adds a dedicated
openSidebarPortLinksInCmuxBrowser setting with its own toggle in
Settings so port and PR link behavior can be controlled independently.

Addresses review feedback from manaflow-ai#1844
israeligal pushed a commit to israeligal/cmux-file-explorer that referenced this pull request May 2, 2026
…(#2219)

Port links were reusing the PR-link preference
(openSidebarPullRequestLinksInCmuxBrowser), causing inconsistent
behavior when users toggled that setting. Adds a dedicated
openSidebarPortLinksInCmuxBrowser setting with its own toggle in
Settings so port and PR link behavior can be controlled independently.

Addresses review feedback from manaflow-ai/cmux#1844

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
rodchristiansen pushed a commit to rodchristiansen/cmux that referenced this pull request Sep 2, 2026
Port links were reusing the PR-link preference
(openSidebarPullRequestLinksInCmuxBrowser), causing inconsistent
behavior when users toggled that setting. Adds a dedicated
openSidebarPortLinksInCmuxBrowser setting with its own toggle in
Settings so port and PR link behavior can be controlled independently.

Addresses review feedback from manaflow-ai#1844
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ports in the Sidebar should be clickable like open PRs

2 participants