Skip to content

feat(browser): add mobile viewport toggle with configurable size#1615

Open
ryuuuuukun wants to merge 1 commit intomanaflow-ai:mainfrom
ryuuuuukun:feat/browser-mobile-viewport-toggle
Open

feat(browser): add mobile viewport toggle with configurable size#1615
ryuuuuukun wants to merge 1 commit intomanaflow-ai:mainfrom
ryuuuuukun:feat/browser-mobile-viewport-toggle

Conversation

@ryuuuuukun
Copy link
Copy Markdown

@ryuuuuukun ryuuuuukun commented Mar 17, 2026

Summary

  • Add a phone icon button to the browser toolbar that toggles mobile viewport emulation (default 390×844, iPhone 14)
  • Switches User-Agent to mobile Safari for accurate responsive rendering
  • Viewport size is configurable via Settings > Browser
  • Localized tooltips (en/ja)

Closes #1614

Screenshots

Desktop Mobile (390×844)
Desktop view Mobile view

Changes

File Description
BrowserPanel.swift Mobile UA string, BrowserMobileViewportSettings, setMobileViewport() method
BrowserPanelView.swift Toggle button with active state highlight, configurable frame constraints
cmuxApp.swift Settings UI for viewport width/height
Localizable.xcstrings en/ja localization entries

Test plan

  • Click the phone icon in browser toolbar → WebView constrains to mobile size
  • Click again → returns to full-width desktop view
  • Change viewport size in Settings > Browser → reflected on next toggle
  • Mobile UA is sent (verify via navigator.userAgent in DevTools console)
  • Tooltip shows current dimensions when hovering the button

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a mobile viewport toggle to switch between mobile and desktop browsing modes.
    • Added editable mobile viewport dimensions (width and height) in Settings.
  • Localization

    • Expanded localized UI strings for menus, commands, settings, prompts, and notifications across multiple languages.
  • Branding

    • Updated app release configuration to a variant with a different product name/identifier.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 17, 2026

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

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a configurable mobile viewport feature: new localization keys, persistent viewport width/height settings, a browser toolbar toggle that switches viewport size and user‑agent and reloads the WebView, UI to edit viewport dimensions in Settings, and minor Xcode project release product metadata updates.

Changes

Cohort / File(s) Summary
Localization
Resources/Localizable.xcstrings
Added many new localization keys and translations for browser/mobile viewport, workspace, settings, commands, update/notifications, and related UI strings across locales.
Mobile Viewport Core Logic
Sources/Panels/BrowserPanel.swift
Introduced BrowserMobileViewportSettings, mobileUserAgent, @Published private(set) var isMobileViewport, setMobileViewport(_:), and ensured navigate/reload paths apply the effective User‑Agent when mobile mode is active.
Mobile Viewport UI
Sources/Panels/BrowserPanelView.swift
Added @AppStorage mobileViewportWidth/Height, a toolbar mobile toggle button (toggles mode, reloads), conditional WebView sizing/background for mobile mode, and updated accessibility/tooltip texts.
Settings Configuration
Sources/cmuxApp.swift
Added @AppStorage bindings for mobile viewport width/height and a Settings row to edit the Width × Height values.
Project Metadata
GhosttyTabs.xcodeproj/project.pbxproj
Updated Release config: PRODUCT_BUNDLE_IDENTIFIER and PRODUCT_NAME changed for the release target.

Sequence Diagram

sequenceDiagram
    participant User
    participant BrowserPanelView as BrowserPanelView
    participant AppStorage
    participant BrowserPanel as BrowserPanel
    participant WebView

    User->>BrowserPanelView: Tap mobile viewport button
    activate BrowserPanelView
    BrowserPanelView->>AppStorage: Read/persist mobileViewportWidth/Height
    BrowserPanelView->>BrowserPanel: setMobileViewport(enabled)
    activate BrowserPanel
    BrowserPanel->>BrowserPanel: Update isMobileViewport & effectiveUserAgent
    alt enabled
        BrowserPanel->>WebView: Set customUserAgent = mobileUserAgent
    else disabled
        BrowserPanel->>WebView: Set customUserAgent = safariUserAgent
    end
    BrowserPanel->>WebView: Trigger reload / navigate
    deactivate BrowserPanel
    BrowserPanelView->>WebView: Resize frame to mobileViewportWidth/Height (if enabled)
    WebView-->>User: Render page with new UA & viewport
    deactivate BrowserPanelView
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I nibbled pixels, made a phone so small,
UA swapped places, the site stood tall.
Width × Height tucked in settings' bed,
Tap, reload — the page dresses red.
Hopping off—responsive bliss, carrot-fed.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning One out-of-scope change detected: PRODUCT_BUNDLE_IDENTIFIER and PRODUCT_NAME in project.pbxproj changed from 'cmux' to 'cmux-for-designers', unrelated to mobile viewport feature. Remove the bundle identifier and product name changes from the PR, or explain why they are necessary for the mobile viewport feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a mobile viewport toggle feature with configurable dimensions.
Description check ✅ Passed The description includes summary, screenshots, file changes, and test plan. However, it lacks the 'Testing' section with manual verification details and 'Demo Video' section required by the template.
Linked Issues check ✅ Passed The PR implements all core requirements from #1614: phone icon button for viewport toggle, mobile UA switching, configurable settings, visual active state, and page reload on toggle.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@ryuuuuukun ryuuuuukun force-pushed the feat/browser-mobile-viewport-toggle branch from 5f49176 to 656f4e7 Compare March 17, 2026 16:22
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
Sources/Panels/BrowserPanelView.swift (2)

756-778: Add explicit accessibility semantics to the icon-only mobile toggle.

On Line 761, VoiceOver currently has to infer meaning from the symbol. Add a localized accessibilityLabel/accessibilityValue so users can hear current mode and target state clearly.

♿ Suggested accessibility improvement
     private var mobileViewportButton: some View {
         Button(action: {
             panel.setMobileViewport(!panel.isMobileViewport)
             panel.reload()
         }) {
             Image(systemName: "iphone")
                 .symbolRenderingMode(.monochrome)
                 .cmuxFlatSymbolColorRendering()
                 .font(.system(size: devToolsButtonIconSize, weight: .medium))
                 .foregroundStyle(panel.isMobileViewport ? cmuxAccentColor() : Color(nsColor: .secondaryLabelColor))
                 .frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
                 .background(
                     RoundedRectangle(cornerRadius: 4)
                         .fill(panel.isMobileViewport ? cmuxAccentColor().opacity(0.2) : Color.clear)
                 )
         }
         .buttonStyle(OmnibarAddressButtonStyle())
         .frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
         .safeHelp(panel.isMobileViewport
             ? String(localized: "browser.mobileViewport.switchToDesktop", defaultValue: "Switch to Desktop View")
             : String(localized: "browser.mobileViewport.switchToMobile", defaultValue: "Switch to Mobile View (\(mobileViewportWidth)×\(mobileViewportHeight))"))
+        .accessibilityLabel(String(localized: "browser.mobileViewport.toggle", defaultValue: "Mobile viewport"))
+        .accessibilityValue(
+            panel.isMobileViewport
+                ? String(localized: "browser.mobileViewport.state.mobile", defaultValue: "Mobile \(mobileViewportWidth)×\(mobileViewportHeight)")
+                : String(localized: "browser.mobileViewport.state.desktop", defaultValue: "Desktop")
+        )
         .accessibilityIdentifier("BrowserMobileViewportButton")
     }

As per coding guidelines, "All user-facing strings must be localized using String(localized: "key.name", defaultValue: "English text") for every string shown in the UI (labels, buttons, menus, dialogs, tooltips, error messages)."

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

In `@Sources/Panels/BrowserPanelView.swift` around lines 756 - 778, The
mobileViewportButton is icon-only so VoiceOver cannot infer state; add explicit
localized accessibility semantics by attaching an accessibilityLabel and
accessibilityValue (or a combined accessibilityLabel describing current mode and
target action) to the Button or its Image. Use String(localized:
"browser.mobileViewport.currentMode", defaultValue: "Mobile View") and
String(localized: "browser.mobileViewport.targetAction", defaultValue: "Switch
to Desktop View") (and the inverse for desktop state) and reference
panel.isMobileViewport to choose which localized strings to supply; keep the
existing safeHelp but add .accessibilityLabel(...) and .accessibilityValue(...)
(or one combined .accessibilityLabel(...)) to the mobileViewportButton so
VoiceOver reads the current mode and the action.

1057-1063: Clamp persisted viewport dimensions before applying frame constraints.

If settings ever persist <= 0, Lines 1058-1059 can collapse or invalidate layout. Defensive clamping here makes the view robust regardless of storage state.

🛡️ Suggested defensive clamp
-        .frame(
-            maxWidth: panel.isMobileViewport ? CGFloat(mobileViewportWidth) : .infinity,
-            maxHeight: panel.isMobileViewport ? CGFloat(mobileViewportHeight) : .infinity,
-            alignment: .topLeading
-        )
+        .frame(
+            maxWidth: panel.isMobileViewport ? CGFloat(max(1, mobileViewportWidth)) : .infinity,
+            maxHeight: panel.isMobileViewport ? CGFloat(max(1, mobileViewportHeight)) : .infinity,
+            alignment: .topLeading
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Panels/BrowserPanelView.swift` around lines 1057 - 1063, Clamp the
persisted viewport dimensions before applying the frame modifiers: when using
panel.isMobileViewport with mobileViewportWidth and mobileViewportHeight in the
.frame(...) calls, ensure the values are defensively clamped to a positive
minimum (e.g., >= 1) so <=0 values from storage don't collapse the layout;
update the code that reads/uses mobileViewportWidth and mobileViewportHeight
(the values passed into the .frame(maxWidth:..., maxHeight:...)) to use a
clampedWidth/clampedHeight derived from max(1, persistedValue) or similar before
applying the frame constraints.
Sources/Panels/BrowserPanel.swift (1)

3865-3872: The sole caller already reloads immediately after toggling, so current behavior is functionally correct, but the API contract remains fragile.

setMobileViewport(_:) is called only in BrowserPanelView.swift (line 758), and that call site explicitly invokes reload() on line 759. The current implementation works without bugs. However, the function design—requiring callers to remember to reload—is fragile and could mislead future maintainers. Consider documenting the reload expectation more prominently, or restructure the API to own the reload when shouldRenderWebView is true.

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

In `@Sources/Panels/BrowserPanel.swift` around lines 3865 - 3872,
setMobileViewport(_:) currently flips isMobileViewport and updates
webView.customUserAgent but leaves reload responsibility to callers, which is
fragile; modify setMobileViewport(_ enabled: Bool) (and its use sites like the
caller in BrowserPanelView.swift) so the method itself triggers reload() when
shouldRenderWebView is true (i.e., after setting isMobileViewport and updating
webView.customUserAgent call reload()), or alternatively add a clear doc comment
on setMobileViewport(_: ) stating it does NOT reload and callers must call
reload()—pick one approach and apply consistently so future callers of
setMobileViewport(_: ) don’t forget to call reload().
🤖 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/cmuxApp.swift`:
- Around line 4243-4249: Replace the three bare string literals used in the
viewport controls with localized strings: change TextField placeholders "W" and
"H" (the TextField initializers bound to mobileViewportWidth and
mobileViewportHeight) to use String(localized:
"settings.viewport.width.placeholder", defaultValue: "W") and String(localized:
"settings.viewport.height.placeholder", defaultValue: "H"), and replace the
separator Text("×") with Text(String(localized: "settings.viewport.separator",
defaultValue: "×")); ensure the localization keys are descriptive and consistent
with other settings keys and that the TextField and Text initializers continue
to use the same values/format arguments.

---

Nitpick comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 3865-3872: setMobileViewport(_:) currently flips isMobileViewport
and updates webView.customUserAgent but leaves reload responsibility to callers,
which is fragile; modify setMobileViewport(_ enabled: Bool) (and its use sites
like the caller in BrowserPanelView.swift) so the method itself triggers
reload() when shouldRenderWebView is true (i.e., after setting isMobileViewport
and updating webView.customUserAgent call reload()), or alternatively add a
clear doc comment on setMobileViewport(_: ) stating it does NOT reload and
callers must call reload()—pick one approach and apply consistently so future
callers of setMobileViewport(_: ) don’t forget to call reload().

In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 756-778: The mobileViewportButton is icon-only so VoiceOver cannot
infer state; add explicit localized accessibility semantics by attaching an
accessibilityLabel and accessibilityValue (or a combined accessibilityLabel
describing current mode and target action) to the Button or its Image. Use
String(localized: "browser.mobileViewport.currentMode", defaultValue: "Mobile
View") and String(localized: "browser.mobileViewport.targetAction",
defaultValue: "Switch to Desktop View") (and the inverse for desktop state) and
reference panel.isMobileViewport to choose which localized strings to supply;
keep the existing safeHelp but add .accessibilityLabel(...) and
.accessibilityValue(...) (or one combined .accessibilityLabel(...)) to the
mobileViewportButton so VoiceOver reads the current mode and the action.
- Around line 1057-1063: Clamp the persisted viewport dimensions before applying
the frame modifiers: when using panel.isMobileViewport with mobileViewportWidth
and mobileViewportHeight in the .frame(...) calls, ensure the values are
defensively clamped to a positive minimum (e.g., >= 1) so <=0 values from
storage don't collapse the layout; update the code that reads/uses
mobileViewportWidth and mobileViewportHeight (the values passed into the
.frame(maxWidth:..., maxHeight:...)) to use a clampedWidth/clampedHeight derived
from max(1, persistedValue) or similar before applying the frame constraints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afcac171-e5ab-4098-80b6-5951ed6d8278

📥 Commits

Reviewing files that changed from the base of the PR and between 8d8fadb and 5f49176.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

2 issues found across 4 files

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="Resources/Localizable.xcstrings">

<violation number="1" location="Resources/Localizable.xcstrings:4760">
P2: Mobile viewport tooltip text is hardcoded to `390px` in localization, causing stale/incorrect help text when viewport dimensions are user-configurable.</violation>
</file>

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

<violation number="1" location="Sources/cmuxApp.swift:3103">
P2: New mobile viewport size settings are persisted but not included in `resetAllSettings()`, so 'Reset All Settings' does not fully reset browser configuration.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
Sources/Panels/BrowserPanel.swift (1)

3422-3425: Centralize UA application to avoid drift across call paths.

UA assignment is repeated in navigation, reload, and toggle paths. A single helper reduces mismatch risk over time.

♻️ Proposed refactor
+    private var effectiveUserAgent: String {
+        isMobileViewport
+            ? BrowserUserAgentSettings.mobileUserAgent
+            : BrowserUserAgentSettings.safariUserAgent
+    }
+
+    private func applyEffectiveUserAgent() {
+        webView.customUserAgent = effectiveUserAgent
+    }

     private func performNavigation(
         request: URLRequest,
         originalURL: URL,
         recordTypedNavigation: Bool,
         preserveRestoredSessionHistory: Bool
     ) {
@@
-        webView.customUserAgent = isMobileViewport
-            ? BrowserUserAgentSettings.mobileUserAgent
-            : BrowserUserAgentSettings.safariUserAgent
+        applyEffectiveUserAgent()
         shouldRenderWebView = true
@@
     func reload() {
-        webView.customUserAgent = isMobileViewport
-            ? BrowserUserAgentSettings.mobileUserAgent
-            : BrowserUserAgentSettings.safariUserAgent
+        applyEffectiveUserAgent()
         webView.reload()
     }
@@
     func setMobileViewport(_ enabled: Bool) {
         isMobileViewport = enabled
-        webView.customUserAgent = enabled
-            ? BrowserUserAgentSettings.mobileUserAgent
-            : BrowserUserAgentSettings.safariUserAgent
+        applyEffectiveUserAgent()
     }

Also applies to: 3859-3861, 3869-3871

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

In `@Sources/Panels/BrowserPanel.swift` around lines 3422 - 3425, Centralize
user-agent assignment by adding a single helper (e.g., applyUserAgent()) that
sets webView.customUserAgent = isMobileViewport ?
BrowserUserAgentSettings.mobileUserAgent :
BrowserUserAgentSettings.safariUserAgent and call this helper from all places
that currently set the UA (navigation handling, reload logic, and the mobile
viewport toggle code paths where isMobileViewport is changed) so the same logic
is used everywhere and drift is prevented; update callers to invoke
applyUserAgent() after any change to isMobileViewport or when
reloading/navigating.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Resources/Localizable.xcstrings`:
- Around line 4757-4766: The localized labels currently hardcode "390px" in the
value strings for both English ("Switch to Mobile View (390px)") and Japanese
("モバイル表示に切替 (390px)"); change those values to a format placeholder (e.g. "Switch
to Mobile View (%@)" and "モバイル表示に切替 (%@)") or to a generic label (e.g. "Switch
to Mobile View") and then append/format the live dimensions in Swift when
displaying (use NSLocalizedString for the key and supply the runtime
width×height string), updating the entries under the same stringUnit keys so
localization tooling stays intact.

In `@Sources/Panels/BrowserPanel.swift`:
- Around line 3865-3867: Update the documentation for setMobileViewport(_:) to
accurately state that it updates mobile emulation state and user-agent only, not
the frame size; remove or change the claim “Only resizes the frame — does NOT
reload the page.” and instead mention that frame/resizing behavior is handled in
BrowserPanelView (e.g., BrowserPanelView.applyFrameConstraints or similar) so
callers know setMobileViewport only toggles UA/viewport state and does not
perform layout changes.

---

Nitpick comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 3422-3425: Centralize user-agent assignment by adding a single
helper (e.g., applyUserAgent()) that sets webView.customUserAgent =
isMobileViewport ? BrowserUserAgentSettings.mobileUserAgent :
BrowserUserAgentSettings.safariUserAgent and call this helper from all places
that currently set the UA (navigation handling, reload logic, and the mobile
viewport toggle code paths where isMobileViewport is changed) so the same logic
is used everywhere and drift is prevented; update callers to invoke
applyUserAgent() after any change to isMobileViewport or when
reloading/navigating.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29cf1ad8-626c-44d7-a31a-2e3082452d72

📥 Commits

Reviewing files that changed from the base of the PR and between 5f49176 and 656f4e7.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift

@ryuuuuukun ryuuuuukun force-pushed the feat/browser-mobile-viewport-toggle branch 2 times, most recently from 7600deb to f88e611 Compare March 17, 2026 16:36
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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/cmuxApp.swift`:
- Around line 4238-4265: The two TextField bindings for mobile viewport
dimensions (the value bindings mobileViewportWidth and mobileViewportHeight used
in the SettingsCardRow) currently allow zero or negative numbers; clamp these
values to a valid positive range (e.g. minimum of 1) by validating on change or
in the property setters so the stored values are replaced with max(1, newValue)
(and optionally clip to a sensible upper bound). Update the handlers around the
TextField bindings (the mobileViewportWidth/mobileViewportHeight change path) to
enforce this constraint so the mobile preview never receives 0/negative
dimensions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e2f374a-59bd-48d4-a191-5f5f8b29039c

📥 Commits

Reviewing files that changed from the base of the PR and between 656f4e7 and f88e611.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Panels/BrowserPanelView.swift

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
Sources/Panels/BrowserPanelView.swift (1)

619-623: Consider keeping the mobile toggle visible on new-tab pages.

With this condition, users can’t turn mobile mode off while on the blank/new-tab state. Keeping mobileViewportButton visible would improve recovery/discoverability.

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

In `@Sources/Panels/BrowserPanelView.swift` around lines 619 - 623, The
conditional hides the mobileViewportButton when panel.isShowingNewTabPage,
preventing users from toggling mobile mode on new-tab pages; change the UI
condition so mobileViewportButton is always included (remove it from the guarded
block) while keeping browserThemeModeButton and developerToolsButton inside the
if !panel.isShowingNewTabPage block, ensuring mobileViewportButton remains
visible even when panel.isShowingNewTabPage is true.
🤖 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/Panels/BrowserPanelView.swift`:
- Around line 756-777: The icon-only mobileViewportButton lacks an explicit
accessibilityLabel and uses a concatenated help string; update the safeHelp and
add an accessibilityLabel (or accessibilityValue/state) for mobileViewportButton
using fully localized format strings via String(localized:..., defaultValue:...)
for both states, e.g. keys for "browser.mobileViewport.switchToDesktop" and a
formatted key like "browser.mobileViewport.switchToMobile.withDimensions" that
accepts width/height (use mobileViewportWidth and mobileViewportHeight when
formatting), and base the label/state on panel.isMobileViewport so VoiceOver
gets a descriptive localized string rather than a raw concatenation.
- Around line 1056-1059: The frame call uses persisted
mobileViewportWidth/mobileViewportHeight directly, which can be <= 0 and
collapse the web surface; before applying them in the .frame for
panel.isMobileViewport (the view using panel.isMobileViewport and the
mobileViewportWidth/mobileViewportHeight symbols), clamp those persisted
dimensions to a safe minimum (e.g., max(1, width) / max(1, height) or a defined
MobileViewport.minimumWidth/Height) and use the clamped CGFloat values in the
maxWidth/maxHeight parameters so stale/corrupt values cannot produce a zero or
negative frame.

---

Nitpick comments:
In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 619-623: The conditional hides the mobileViewportButton when
panel.isShowingNewTabPage, preventing users from toggling mobile mode on new-tab
pages; change the UI condition so mobileViewportButton is always included
(remove it from the guarded block) while keeping browserThemeModeButton and
developerToolsButton inside the if !panel.isShowingNewTabPage block, ensuring
mobileViewportButton remains visible even when panel.isShowingNewTabPage is
true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1bd3b035-c3fe-46fe-90f1-ba712da239cf

📥 Commits

Reviewing files that changed from the base of the PR and between f88e611 and fec9e30.

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

@ryuuuuukun ryuuuuukun force-pushed the feat/browser-mobile-viewport-toggle branch from fec9e30 to d14d42c Compare March 18, 2026 06:14
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
Sources/Panels/BrowserPanel.swift (1)

3982-3984: ⚠️ Potential issue | 🟡 Minor

setMobileViewport docs are still inaccurate.

Line 3983 says this method “resizes the frame,” but this function only updates viewport mode state + UA. That mismatch can mislead call sites.

📝 Suggested doc-only fix
-    /// Toggle mobile viewport emulation (iPhone 14 equivalent UA + 390px viewport width).
-    /// Only resizes the frame — does NOT reload the page.
+    /// Toggle mobile viewport emulation by updating panel state and User-Agent.
+    /// Frame constraints are handled by BrowserPanelView; callers decide whether to reload.
     func setMobileViewport(_ enabled: Bool) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Panels/BrowserPanel.swift` around lines 3982 - 3984, The docstring
for setMobileViewport is misleading: it claims the method "resizes the frame —
does NOT reload the page" but the implementation only updates viewport mode
state and the user agent string. Update the documentation on the
setMobileViewport(_:) function to accurately state that it toggles the internal
mobile viewport mode and user-agent (e.g., sets viewportMode and updates UA)
without changing the window/frame size or triggering a reload, and remove the
wording about resizing the frame.
Sources/cmuxApp.swift (1)

4784-4800: ⚠️ Potential issue | 🟠 Major

Localize the viewport control literals and enforce positive dimension bounds.

This block reintroduces previously flagged issues: bare user-facing literals ("W", "×", "H") and unrestricted numeric inputs that can store invalid values (e.g., 0/negative).

💡 Proposed fix
                         SettingsCardRow(
                             String(localized: "settings.browser.mobileViewport", defaultValue: "Mobile Viewport Size"),
                             subtitle: String(localized: "settings.browser.mobileViewport.subtitle", defaultValue: "Width × Height used for the mobile viewport toggle.")
                         ) {
                             HStack(spacing: 4) {
-                                TextField("W", value: $mobileViewportWidth, format: .number)
+                                TextField(
+                                    String(localized: "settings.browser.mobileViewport.widthPlaceholder", defaultValue: "W"),
+                                    value: $mobileViewportWidth,
+                                    format: .number
+                                )
                                     .textFieldStyle(.roundedBorder)
                                     .frame(width: 64)
                                     .multilineTextAlignment(.center)
-                                Text("×")
+                                Text(String(localized: "settings.browser.mobileViewport.separator", defaultValue: "×"))
                                     .foregroundStyle(.secondary)
-                                TextField("H", value: $mobileViewportHeight, format: .number)
+                                TextField(
+                                    String(localized: "settings.browser.mobileViewport.heightPlaceholder", defaultValue: "H"),
+                                    value: $mobileViewportHeight,
+                                    format: .number
+                                )
                                     .textFieldStyle(.roundedBorder)
                                     .frame(width: 64)
                                     .multilineTextAlignment(.center)
                             }
+                            .onChange(of: mobileViewportWidth) { newValue in
+                                mobileViewportWidth = min(max(newValue, 1), 4096)
+                            }
+                            .onChange(of: mobileViewportHeight) { newValue in
+                                mobileViewportHeight = min(max(newValue, 1), 4096)
+                            }
                         }

As per coding guidelines: “All user-facing strings must be localized using String(localized: "key.name", defaultValue: "English text") … Never use bare string literals in SwiftUI Text(), Button(), alert titles, etc.”

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

In `@Sources/cmuxApp.swift` around lines 4784 - 4800, Replace bare literals in the
viewport control with localized strings and prevent zero/negative sizes: change
the "W", "H", and "×" Text/TextField placeholders to use String(localized: ...,
defaultValue: ...) keys (e.g., "settings.browser.mobileViewport.width.short",
"settings.browser.mobileViewport.height.short",
"settings.browser.mobileViewport.times") and ensure the bound state variables
mobileViewportWidth and mobileViewportHeight cannot become <= 0 by clamping them
to a minimum (e.g., 1) when they change (use .onChange or a didSet/validation
helper) and/or enforce a minimum in the input handling where SettingsCardRow and
the two TextField bindings are defined so only positive integers are stored.
Resources/Localizable.xcstrings (1)

4947-4953: ⚠️ Potential issue | 🟡 Minor

Remove hardcoded 390px from the mobile-toggle label.

Line 4947 and Line 4953 hardcode the default width, so the text can drift from user-configured viewport dimensions.

Proposed localization fix
-            "value": "Switch to Mobile View (390px)"
+            "value": "Switch to Mobile View (%@)"
...
-            "value": "モバイル表示に切替 (390px)"
+            "value": "モバイル表示に切替 (%@)"

Then pass the live dimension string from Swift (for example, 390×844) when rendering this label.

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

In `@Resources/Localizable.xcstrings` around lines 4947 - 4953, Update the
localized mobile-toggle label in Resources/Localizable.xcstrings by removing the
hardcoded "390px" text (e.g., replace "Switch to Mobile View (390px)" /
"モバイル表示に切替 (390px)" with a placeholder-based string such as "Switch to Mobile
View (%@)" and corresponding Japanese placeholder) and then pass the live
dimension string (e.g., "390×844") from the Swift UI code when setting the label
(where the label is created/updated) so the displayed value reflects the current
configured viewport dimension.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@GhosttyTabs.xcodeproj/project.pbxproj`:
- Around line 900-901: Revert the unintended product name/identifier changes:
restore PRODUCT_BUNDLE_IDENTIFIER to com.cmuxterm.app and PRODUCT_NAME to cmux
in the project.pbxproj, and also restore TEST_HOST to reference
"$(BUILT_PRODUCTS_DIR)/cmux.app/Contents/MacOS/cmux"; if the new name is
intentional, instead update all dependent scripts and config to match (update
scripts/build-sign-upload.sh's APP_PATH, appcast/Sparkle identifiers, and
scripts/reload.sh, reloadp.sh, rebuild.sh) and ensure Sparkle's bundle
identifier matches the new PRODUCT_BUNDLE_IDENTIFIER so auto-updates continue to
work.

In `@Sources/cmuxApp.swift`:
- Around line 3571-3572: resetAllSettings() does not restore the new mobile
viewport AppStorage values; update resetAllSettings() to explicitly reset
mobileViewportWidth and mobileViewportHeight back to
BrowserMobileViewportSettings.defaultWidth and
BrowserMobileViewportSettings.defaultHeight (or clear their AppStorage keys) so
the persisted viewport dimensions are returned to defaults when "Reset All
Settings" runs.

---

Duplicate comments:
In `@Resources/Localizable.xcstrings`:
- Around line 4947-4953: Update the localized mobile-toggle label in
Resources/Localizable.xcstrings by removing the hardcoded "390px" text (e.g.,
replace "Switch to Mobile View (390px)" / "モバイル表示に切替 (390px)" with a
placeholder-based string such as "Switch to Mobile View (%@)" and corresponding
Japanese placeholder) and then pass the live dimension string (e.g., "390×844")
from the Swift UI code when setting the label (where the label is
created/updated) so the displayed value reflects the current configured viewport
dimension.

In `@Sources/cmuxApp.swift`:
- Around line 4784-4800: Replace bare literals in the viewport control with
localized strings and prevent zero/negative sizes: change the "W", "H", and "×"
Text/TextField placeholders to use String(localized: ..., defaultValue: ...)
keys (e.g., "settings.browser.mobileViewport.width.short",
"settings.browser.mobileViewport.height.short",
"settings.browser.mobileViewport.times") and ensure the bound state variables
mobileViewportWidth and mobileViewportHeight cannot become <= 0 by clamping them
to a minimum (e.g., 1) when they change (use .onChange or a didSet/validation
helper) and/or enforce a minimum in the input handling where SettingsCardRow and
the two TextField bindings are defined so only positive integers are stored.

In `@Sources/Panels/BrowserPanel.swift`:
- Around line 3982-3984: The docstring for setMobileViewport is misleading: it
claims the method "resizes the frame — does NOT reload the page" but the
implementation only updates viewport mode state and the user agent string.
Update the documentation on the setMobileViewport(_:) function to accurately
state that it toggles the internal mobile viewport mode and user-agent (e.g.,
sets viewportMode and updates UA) without changing the window/frame size or
triggering a reload, and remove the wording about resizing the frame.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6d41bbf-263e-4aeb-9408-0a08dcd26d10

📥 Commits

Reviewing files that changed from the base of the PR and between fec9e30 and d14d42c.

📒 Files selected for processing (5)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/cmuxApp.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Panels/BrowserPanelView.swift

@oskarscholander
Copy link
Copy Markdown

oskarscholander commented Mar 26, 2026

This! Please merge! Need this every day!

@austinywang promise you, best feature ever :D

@ryuuuuukun
Copy link
Copy Markdown
Author

Conflicts have been resolved. Could you please review and merge when you have a chance? Thank you!

Add a phone icon button to the browser toolbar that toggles mobile
viewport emulation (default 390×844, iPhone 14). Switches User-Agent
to mobile Safari for accurate responsive rendering. Viewport size is
configurable via Settings > Browser. Localized tooltips (en/ja).

Closes manaflow-ai#1614

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ryuuuuukun ryuuuuukun force-pushed the feat/browser-mobile-viewport-toggle branch from d5d29b8 to 7db7ba4 Compare April 12, 2026 14:28
@ryuuuuukun
Copy link
Copy Markdown
Author

Rebased onto latest main and squashed into a single clean commit. The previous branch had stale merge commits and unrelated noise mixed in — all cleaned up now.

@austinywang Would appreciate a review when you get a chance!

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.

Feature request: Mobile viewport toggle for browser panel

2 participants