Skip to content

feat(mobile): Capacitor app (iOS + Android) + mobile-responsive fixes - #11505

Closed
nmgarza5 wants to merge 5 commits into
mainfrom
nikg/onyx-ios
Closed

feat(mobile): Capacitor app (iOS + Android) + mobile-responsive fixes#11505
nmgarza5 wants to merge 5 commits into
mainfrom
nikg/onyx-ios

Conversation

@nmgarza5

@nmgarza5 nmgarza5 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Description

Cross-platform Onyx mobile app via Capacitor (one codebase → iOS + Android), plus mobile UX fixes for the end-user chat experience. Admin stays desktop.

mobile/ (Capacitor 8; appId app.onyx.mobile):

  • Wraps cloud.onyx.app now; will point at a mobile-first Opal build of web/ (Figma) next.
  • Google SSO: per-platform overrideUserAgent (avoids reCAPTCHA disallowed_useragent).
  • Native ios//android/ generated via cap add (gitignored), SPM on iOS (no CocoaPods).
  • CI (pr-mobile-build.yml): iOS simulator + Android debug builds on PR; tag v*.*.* → signed iOS archive → TestFlight via an App Store Connect API key (AWS Secrets Manager, same store/OIDC as desktop).

web/ (responsive; also fixes web on mobile):

  • Sidebar defaults collapsed on mobile; settings header top spacing tightened.
  • Multi-model chat disabled on mobile (unusable at phone widths).

iOS verified building + running on the simulator. Remaining for release: add deploy/apple-asc-* secrets; Apple app record; Android Play signing (follow-up).

How Has This Been Tested?

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

…fixes

Native iOS shell wrapping Onyx Cloud, plus mobile UX fixes for the end-user
chat experience. Admin stays desktop.

mobile/ (new; XcodeGen + xcodebuild; bundle app.onyx.ios):
- SwiftUI WKWebView loading cloud.onyx.app with loading/offline states
- Google SSO: mobile Safari UA to avoid reCAPTCHA disallowed_useragent
- App icon; SwiftUI safe-area handling
- Injected mobile.css: collapse sidebar on phones, dialogs scroll/fit,
  settings two-pane -> stacked, momentum scroll, wide-table scroll wrappers
- Admin pages show a native "best viewed on desktop" notice via a JS->native
  route bridge (onyxNav message handler)

web/ (responsive; also fixes web on mobile):
- SidebarStateProvider: default collapsed on mobile when no saved preference
- SettingsLayouts.Header: smaller top spacing on mobile
@nmgarza5
nmgarza5 requested a review from a team as a code owner May 28, 2026 21:49
@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Status Preview Commit Updated
https://onyx-preview-3jxznpujh-danswer.vercel.app c48c9aa 2026-05-28 22:34:54 UTC

@greptile-apps

greptile-apps Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a native iOS app (mobile/) wrapping the Onyx Cloud web UI in a SwiftUI WKWebView shell, alongside several responsive fixes to the web app itself.

  • mobile/: New SwiftUI app (XcodeGen-based) that loads cloud.onyx.app, injects mobile CSS for layout overrides, bridges SPA route changes to a native admin-page overlay, defaults the sidebar to collapsed, and includes a GitHub Actions workflow for simulator CI builds and TestFlight releases.
  • web/: Three targeted responsive fixes — sidebar defaults to collapsed on first mobile load, settings header switches from a fixed spacer to pt-4 md:pt-10, and multi-model comparison is disabled on phone widths.

Confidence Score: 4/5

The web-side changes are safe to merge. The mobile CI workflow has a broken release path that will fail on any semver tag.

The release-ios CI job uploads to TestFlight using xcrun altool --upload-app with Apple ID and app-specific password — an authentication method Apple retired in October 2023. Every release-tag build will fail at that step. The simulator build job and all web-side changes are unaffected and work correctly.

.github/workflows/pr-ios-build.yml — the release-ios job TestFlight upload step needs to be replaced with App Store Connect API key auth.

Important Files Changed

Filename Overview
.github/workflows/pr-ios-build.yml New CI workflow for iOS builds; simulator build job is solid, but the release job uses xcrun altool --upload-app with username/password auth which Apple stopped accepting in 2023 — the TestFlight upload step will fail on any release tag.
mobile/Sources/OnyxApp.swift SwiftUI WKWebView shell with CSS injection, SPA route bridging, and admin overlay; createWebViewWith opens all target=_blank URLs (including external) in the authenticated WebView instead of Safari.
mobile/Sources/mobile.css Mobile CSS overrides injected by the shell; negative margin-top on [aria-label=admin-page-title] was flagged in a prior thread as overcorrecting now that the pt-4 md:pt-10 fix ships in the same PR.
mobile/project.yml XcodeGen spec defining bundle ID, deployment target (iOS 16), and build settings; signing disabled in base (overridden in CI). No issues found.
web/lib/opal/src/layouts/settings/components.tsx Replaces a fixed Spacer rem=2.5 with pt-4 md:pt-10 responsive padding on the settings header container; straightforward responsive fix.
web/src/layouts/sidebar-layouts.tsx Adds a fallback that defaults the sidebar to collapsed on mobile when no saved preference exists; uses the existing MOBILE_SIDEBAR_BREAKPOINT_PX constant correctly.
web/src/refresh-components/popovers/ModelSelector.tsx Disables multi-model comparison mode on mobile via useScreenSize().isMobile; clean and correct.

Comments Outside Diff (1)

  1. .github/workflows/pr-ios-build.yml, line 192-197 (link)

    P1 altool --upload-app with password auth is gone

    Apple deprecated xcrun altool --upload-app --username/--password and stopped accepting these requests in October 2023. On the macos-15 runner (Xcode 16), altool no longer processes TestFlight/App Store uploads via Apple ID + app-specific password. Any release-tag build will fail at this step with an auth error.

    The current replacement is either (a) switch to App Store Connect API Key credentials (--apiKey/--apiIssuer/--apiKeyPath) in the altool call, or (b) set destination: upload in ExportOptions.plist and let xcodebuild -exportArchive drive the upload directly using an authenticationKeyID / authenticationKeyIssuerID pair — which removes the need for a separate upload step entirely.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: .github/workflows/pr-ios-build.yml
    Line: 192-197
    
    Comment:
    **`altool --upload-app` with password auth is gone**
    
    Apple deprecated `xcrun altool --upload-app --username/--password` and stopped accepting these requests in October 2023. On the `macos-15` runner (Xcode 16), `altool` no longer processes TestFlight/App Store uploads via Apple ID + app-specific password. Any release-tag build will fail at this step with an auth error.
    
    The current replacement is either (a) switch to App Store Connect API Key credentials (`--apiKey`/`--apiIssuer`/`--apiKeyPath`) in the `altool` call, or (b) set `destination: upload` in `ExportOptions.plist` and let `xcodebuild -exportArchive` drive the upload directly using an `authenticationKeyID` / `authenticationKeyIssuerID` pair — which removes the need for a separate upload step entirely.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
.github/workflows/pr-ios-build.yml:192-197
**`altool --upload-app` with password auth is gone**

Apple deprecated `xcrun altool --upload-app --username/--password` and stopped accepting these requests in October 2023. On the `macos-15` runner (Xcode 16), `altool` no longer processes TestFlight/App Store uploads via Apple ID + app-specific password. Any release-tag build will fail at this step with an auth error.

The current replacement is either (a) switch to App Store Connect API Key credentials (`--apiKey`/`--apiIssuer`/`--apiKeyPath`) in the `altool` call, or (b) set `destination: upload` in `ExportOptions.plist` and let `xcodebuild -exportArchive` drive the upload directly using an `authenticationKeyID` / `authenticationKeyIssuerID` pair — which removes the need for a separate upload step entirely.

### Issue 2 of 2
mobile/Sources/OnyxApp.swift:287-294
**External links open inside the authenticated WebView**

The `createWebViewWith` delegate loads every `target=_blank` link — including external URLs from chat responses — inside the same WebView rather than forwarding to Safari. Any link clicked in a chat answer or response body will render in the app's authenticated session instead of the user's default browser. For non-Onyx hosts this is surprising UX, and for malicious links it replaces an isolated Safari sandbox with the app's own authenticated context. Dispatching off-origin URLs to `UIApplication.shared.open` preserves the expected behavior while still handling any same-origin popups in-app.

```suggestion
        // Open target=_blank links: same-origin in the web view, external in Safari.
        func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
                     for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
            if let url = navigationAction.request.url {
                if url.host == onyxURL.host {
                    webView.load(URLRequest(url: url))
                } else {
                    UIApplication.shared.open(url)
                }
            }
            return nil
        }
```

Reviews (2): Last reviewed commit: "feat(mobile): disable multi-model chat o..." | Re-trigger Greptile

Comment thread mobile/Sources/mobile.css Outdated
Comment on lines +44 to +53
NOTE: remove once the web/ responsive spacing fix (pt-4 md:pt-10) is deployed. */
[aria-label="admin-page-title"] {
margin-top: -1.5rem !important;
}

/* End-user Settings is a desktop two-pane row (left tab nav + content). Stack it
vertically on mobile so the nav and content each get full width. */
[data-testid="settings-left-tab-navigation"] {
min-width: 0 !important;
width: 100% !important;

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 Negative-margin workaround is now overcorrecting

The comment says "remove once the web/ responsive spacing fix (pt-4 md:pt-10) is deployed" — but that fix is being deployed in this same PR (components.tsx). Once this code lands, the settings header has pt-4 (16 px) on mobile instead of the old 2.5 rem spacer. Applying margin-top: -1.5rem (-24 px) to [aria-label="admin-page-title"] on top of only 16 px of padding results in the title being drawn ~8 px above the padded area, overlapping whatever precedes it. This rule should not be included now that the underlying spacing is corrected in the same changeset.

Prompt To Fix With AI
This is a comment left during a code review.
Path: mobile/Sources/mobile.css
Line: 44-53

Comment:
**Negative-margin workaround is now overcorrecting**

The comment says "remove once the web/ responsive spacing fix (`pt-4 md:pt-10`) is deployed" — but that fix is being deployed in this same PR (`components.tsx`). Once this code lands, the settings header has `pt-4` (16 px) on mobile instead of the old 2.5 rem spacer. Applying `margin-top: -1.5rem` (-24 px) to `[aria-label="admin-page-title"]` on top of only 16 px of padding results in the title being drawn ~8 px above the padded area, overlapping whatever precedes it. This rule should not be included now that the underlying spacing is corrected in the same changeset.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread mobile/Sources/OnyxApp.swift Outdated
import SwiftUI
import WebKit

let onyxURL = URL(string: "https://cloud.onyx.app")!

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.

P2 App is unconditionally bound to cloud.onyx.app

The target URL is a compile-time constant with no override path for self-hosted deployments. Anyone running Onyx on-prem (single-tenant or multi-tenant) cannot use this app without forking and rebuilding. The repository's engineering rules call out that code must consider both cloud and self-hosted deployments. Even a ONYX_BASE_URL build setting (similar to how ONYX_PATH is already plumbed for QA paths) or a per-launch env var pattern would address this without adding runtime UI complexity.

Prompt To Fix With AI
This is a comment left during a code review.
Path: mobile/Sources/OnyxApp.swift
Line: 4

Comment:
**App is unconditionally bound to `cloud.onyx.app`**

The target URL is a compile-time constant with no override path for self-hosted deployments. Anyone running Onyx on-prem (single-tenant or multi-tenant) cannot use this app without forking and rebuilding. The repository's engineering rules call out that code must consider both cloud and self-hosted deployments. Even a `ONYX_BASE_URL` build setting (similar to how `ONYX_PATH` is already plumbed for QA paths) or a per-launch env var pattern would address this without adding runtime UI complexity.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


<div className="flex flex-col gap-6 px-4">
{/* Smaller top gap on mobile; full desktop spacing at md+. */}
<div className="flex flex-col gap-6 px-4 pt-4 md:pt-10">

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.

P2 Opal component modified without updating the Storybook story

SettingsHeader is an Opal layout component and the project rule requires the related story to be updated when a component changes. The existing story in SettingsLayouts.stories.tsx has no mobile-viewport story showing the new pt-4 / md:pt-10 responsive spacing. Adding a story with a mobile viewport (e.g. parameters: { viewport: { defaultViewport: 'mobile1' } }) would keep visual coverage accurate.

Rule Used: What: When modifying a frontend component (Opal, r... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: web/lib/opal/src/layouts/settings/components.tsx
Line: 129

Comment:
**Opal component modified without updating the Storybook story**

`SettingsHeader` is an Opal layout component and the project rule requires the related story to be updated when a component changes. The existing story in `SettingsLayouts.stories.tsx` has no mobile-viewport story showing the new `pt-4` / `md:pt-10` responsive spacing. Adding a story with a mobile viewport (e.g. `parameters: { viewport: { defaultViewport: 'mobile1' } }`) would keep visual coverage accurate.

**Rule Used:** What: When modifying a frontend component (Opal, r... ([source](https://app.greptile.com/personal-org-210/-/custom-context?memory=01f53a66-6321-4d0a-b8c2-d6c3bc03b286))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Mirrors the desktop build flow for the native iOS app:
- PR / merge_group on mobile/** -> xcodegen + unsigned simulator build (compile check)
- tag v*.*.* (non-beta) -> signed device archive + export + TestFlight upload

Signing reuses the desktop mechanism (deployment.yml): Apple secrets pulled from AWS
Secrets Manager via OIDC (deploy/apple-* keys) + certificate imported into a keychain;
upload via altool with the existing APPLE_ID/APPLE_PASSWORD. iOS adds one secret,
deploy/apple-ios-provisioning-profile (App Store profile for app.onyx.ios).
@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Report

Project Changed Added Removed Unchanged Report
admin 5 0 0 171 View Report
exclusive 0 0 0 8 ✅ No changes

nmgarza5 added 3 commits May 28, 2026 15:31
Side-by-side multi-model comparison is unusable at phone widths. Gate it off in
ModelSelector (multiModelAllowed = false when isMobile), so a second model can't be
added on mobile regardless of the workspace setting.
Replace the iOS-only SwiftUI WKWebView shell with a Capacitor project so one codebase
targets iOS + Android. Loads cloud.onyx.app via server.url for now (will point at a
mobile-first Opal build of web/ once those screens exist).

- appId app.onyx.mobile (iOS bundle + Android package)
- Google SSO: per-platform overrideUserAgent (mobile Safari/Chrome) to avoid reCAPTCHA
  disallowed_useragent in the web view
- Capacitor 8 (SPM on iOS, no CocoaPods); native ios/ + android/ generated via `cap add`
  (gitignored), not committed
- Removes the SwiftUI shell + the xcodegen-based pr-ios-build.yml (Capacitor CI lands next)
- PR/merge on mobile/**: cap add + unsigned iOS simulator build and Android debug
  assemble (compile checks on both platforms; ubuntu-latest ships the Android SDK/JDK)
- tag v*.*.* (non-beta): signed iOS archive -> TestFlight via an App Store Connect API
  key (automatic signing, -allowProvisioningUpdates -> no manual cert/profile)

API key pulled from AWS Secrets Manager (deploy/apple-asc-*), same store/OIDC as the
desktop release. Inert until those secrets exist. Android Play release is a follow-up.
@nmgarza5 nmgarza5 changed the title feat(mobile): native iOS app (SwiftUI WKWebView) + mobile-responsive fixes feat(mobile): Capacitor app (iOS + Android) + mobile-responsive fixes May 29, 2026
@nmgarza5 nmgarza5 closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant