Skip to content

feat(mobile): enable landscape rotation with safe-area-aware layouts - #6025

Open
iscekic wants to merge 1 commit into
mainfrom
kwf/enable-landscape-mode-in-the-93e3
Open

feat(mobile): enable landscape rotation with safe-area-aware layouts#6025
iscekic wants to merge 1 commit into
mainfrom
kwf/enable-landscape-mode-in-the-93e3

Conversation

@iscekic

@iscekic iscekic commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Changelog for users

  • The app now rotates between portrait and landscape on iPhone and Android phones; iPad Split View and Slide Over stay out of scope.
  • Rotating keeps your current screen, open session, and any unsent chat draft.
  • The sessions list, search field, New session button, chat composer, transcript, and tab bar clear the notch and camera housing in landscape.
  • Blur backgrounds behind the tab bar and composer stay full-bleed while only their content insets.
  • PR review stays usable in landscape: the Files tab header, its loading skeleton, file list, discussion, floating actions, and inbox rows clear the sensor housing.
  • Screen and sheet headers keep back, title, and trailing controls clear of the sensor on every screen and modal.
  • The image viewer now rotates in landscape: the photo fits the visible area, and Close and Share clear the sensor.
  • Android rotation no longer flashes a foreign white or black frame; portrait layout is unchanged everywhere.

Changelog for maintainers

  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list.tsx:138 — accepted: the loaded list already padded by side insets, but the Files-tab header and loading skeleton rendered edge-to-edge under the landscape sensor housing. Fixed in pr-diff-file-list-header.tsx and pr-diff-file-list-loading.tsx: each applies left/right safe-area insets only when nonzero. The header wraps the row in an inner View so the insets add to the px-4 gutter; background and hairline stay full-width on the outer container. The skeleton matches the loaded FlashList's content-container side padding; its height-feeding bottom padding is unchanged. Zero insets collapse both styles, so portrait geometry is unchanged.
  • Config contract: orientation: 'default' with ios.requireFullScreen: true (full-screen rotation yes, iPad multitasking no); scripts/assert-expo-config.mjs now asserts orientation, requireFullScreen, and the new rotation plugin.
  • New Expo config plugin plugins/withAndroidRotationSurface pins AppTheme's android:windowBackground to the app tokens (#FBFAF5 light, #0E0E10 dark via values-night) so the Android surface-resize gap renders app colors.
  • Root surfaces (gesture root in app-root-providers.tsx, the root Slot wrapper, AppContentReveal) gained bg-background to cover relayout frames.
  • One layout pattern throughout: side insets are horizontal-only and added on inner wrappers or inline styles so className gutters (px-4/px-6) survive; they are spread only when nonzero, keeping portrait pixel-identical. prDiffListBottomPadding gained a pure prDiffListContentPadding helper; the measured floating-bar height and list bottom clearance are unchanged because the insets are horizontal-only.
  • Review first: getTabBarHorizontalInset in src/lib/tab-bar-layout.ts, usePrDiffListContentPadding, the shared wrapper treatment in screen-header.tsx, sheet-header.tsx, and image-viewer-modal.tsx, plus the repair files pr-diff-file-list-header.tsx and pr-diff-file-list-loading.tsx.
  • Riskiest: the image viewer's supportedOrientations={['portrait','landscape']} (React Native otherwise locks full-screen iPhone modals to portrait and clipped the photo), and FlashList content-container styles now depend on insets (session-message-list.tsx keeps the module-level style reference in portrait to preserve list behavior).
  • Unit and mounted tests cover each surface's portrait no-op and landscape padding, including rotation-without-remount for the search header and composer draft.

E2E proof

[e1] Signed-out landscape (empty-state surface) — Android emulator-5556: login rotated portrait→landscape→portrait (e1.mp4 6.4s; e1-landscape.png; e1-portrait-return.png). Email, Continue, and Sign in with Google stay visible and tappable in landscape; portrait return matches the pre-rotation form. UX-PREEXISTING: [pre-existing] landscape login — More sign-in options sits on the home-indicator edge until scrolled (e1-landscape.png vs e1-landscape-scrolled.png).

e1.mp4.trim.mp4

[e1] Signed-out landscape (empty-state surface) — e2e-mobile-app/e1-landscape.png

[e1] Signed-out landscape (empty-state surface) — e2e-mobile-app/e1-landscape-scrolled.png

[e1] Agents list landscape rotation (android) — Android emulator-5554: Agents empty state rotated portrait→landscape→portrait (e1.mp4; e1-agents-landscape.png; e1-agents-portrait-return.png). Landscape keeps Nothing running right now, New session, and the tab icons with a left inset (Home tab x=128 vs x=0 in portrait); portrait return matches the portrait baseline. This capture replaces the earlier sessions-list rotation follow-up that was not proved live. Tooling gap: record.sh stop is not listed in e2e-index.

e1.mp4.trim.mp4

[e1] Agents list landscape rotation (android) — e2e-mobile-app/e1-agents-landscape.png

[e1] Agents list landscape rotation (android) — e2e-mobile-app/e1-agents-portrait-return.png

[e2] New session draft and model sheet in landscape — Android: the unsent draft landscape-draft-e2 survived landscape and portrait return; the return's Go back bounds [0,149][101,250] match pre-rotation and the landscape composer header sits inset (Go back x=128) (e2-composer-landscape.png). The model picker baseline was captured in portrait (e2-sheet-portrait.png); landscape keeps Select model, Done, Search models, and rows reachable (e2-sheet-landscape.png); Done closed the sheet (e2-sheet-closed-landscape.png). This run re-proves the earlier form-sheet pass, adds the landscape composer header inset, and evidences the earlier not-proved-live chat-draft and modal-sheet rotation follow-ups.

e2.mp4.trim.mp4

[e2] New session draft and model sheet landscape (android) — e2e-mobile-app/e2-composer-landscape.png

[e2] New session draft and model sheet landscape (android) — e2e-mobile-app/e2-sheet-landscape.png

[e1] Rotation preserves screen, session, and unsent input — prior/e1-session-landscape-draft-nokeyboard.png

[e2] PR review in landscape — Android emulator-5554 with github-stub Mixed discussion fixture: Overview/Files/Discussion tabs and file navigator (src/alpha.ts, src/beta.ts) visible in landscape (e2-files-loaded.png); reply composer input stays visible in landscape (e2-reply-keyboard.png); portrait return restores tabs and Reply (e2-return-portrait.png, e2-trimmed.mp4). Superseded: this pre-repair capture no longer proves Files-header clearance; the [e3] capture below replaces that claim.

e2-trimmed.mp4.trim.mp4

[e3] PR review Files header landscape (android) — Android emulator-5554 with github-stub Mixed fixture kilo-stub/discussion-mixed#1: the landscape Files tab shows the patched header (Files · 0 of 2 viewed) and Open file navigator at x=165 alongside src/alpha.ts and src/beta.ts (e3-files.xml; e3-files.png); portrait return shows the navigator at x=37 (e3-files-portrait.xml). Inbox is also usable in landscape (e3-inbox-landscape.png). Limitation: the loading skeleton was not held live — Overview→Files served a cached list and e2e-index has no GitHub file-list latency hook.

[e3] PR review Files header landscape (android) — e2e-mobile-app/e3-inbox-landscape.png

Owner request

Enable landscape mode in the mobile app on iOS and Android.
Allow rotation between portrait and landscape and preserve the current screen, session, and unsent input.
Adapt existing layouts so content and controls remain usable in landscape.
Respect safe areas and keyboard visibility.
Check the sessions list, chat, PR review, navigation, and modal screens for clipping or inaccessible controls.
Preserve portrait behavior.

Use the existing app configuration and layout patterns.
Verify rotation and representative screens locally on the platforms affected by the change.
Include a recording, or screenshots if recording is unavailable, in the PR body.
Do not commit fixtures, E2E-only product code, test hooks, or test-only runtime flags.
Deliver a reviewed, CI-green PR and assign it to @iscekic.
Do not merge the PR.

E2E proof — log excerpts

[e2] New session draft and model sheet landscape (android) -> pass :: Android: unsent draft landscape-draft-e2 survived landscape and portrait return (e2-composer-landscape.xml text="landscape-draft-e2"; e2-portrait-return.xml same text, Go back bounds [0,149][101,250] matching pre-rotation). Landscape composer header insets (Go back [128,84][229,185]) (e2-composer-landscape.png). Model sheet in landscape keeps Select model, Done, Search models..., Auto Efficient (e2-sheet-landscape.png).
[e3] PR review Files header landscape (android) -> pass :: Android emulator-5554 with github-stub Mixed fixture kilo-stub/discussion-mixed#1: landscape Files tab shows patched header Files · 0 of 2 viewed and Open file navigator at x=165 plus src/alpha.ts and src/beta.ts (e3-files.xml; e3-files.png); portrait return Open file navigator at x=37 (e3-files-portrait.xml). Inbox also usable in landscape (e3-inbox-landscape.png). Loading skeleton not held (Overview→Files served cached list; no GitHub file-list latency hook in e2e-index).
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-review-0147/e2e-mobile-app/e2-composer-landscape.xml
                                          </com.horcrux.svg.GroupView>
                                        </com.horcrux.svg.SvgView>
                                      </android.widget.Button>
<redacted>
<redacted>
                                    </android.view.ViewGroup>
                                  </android.widget.ScrollView>
                                </android.view.ViewGroup>
                              </android.view.ViewGroup>
                            </android.widget.ScrollView>
                          </android.view.ViewGroup>
                        </android.view.ViewGroup>
                      </android.view.ViewGroup>
                    </android.view.ViewGroup>
                  </android.view.ViewGroup>
                </android.view.ViewGroup>
              </android.widget.FrameLayout>
            </android.widget.FrameLayout>
          </android.widget.FrameLayout>
        </android.widget.LinearLayout>
      </android.widget.FrameLayout>
    </android.widget.LinearLayout>
  </android.widget.FrameLayout>
</hierarchy>
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-review-0147/e2e-mobile-app/e2-portrait-return.xml
<redacted>
<redacted>
<redacted>
<redacted>
                                      </android.widget.Button>
                                    </android.view.ViewGroup>
                                  </android.widget.ScrollView>
                                </android.view.ViewGroup>
                              </android.view.ViewGroup>
                            </android.widget.ScrollView>
                          </android.view.ViewGroup>
                        </android.view.ViewGroup>
                      </android.view.ViewGroup>
                    </android.view.ViewGroup>
                  </android.view.ViewGroup>
                </android.view.ViewGroup>
              </android.widget.FrameLayout>
            </android.widget.FrameLayout>
          </android.widget.FrameLayout>
        </android.widget.LinearLayout>
      </android.widget.FrameLayout>
    </android.widget.LinearLayout>
  </android.widget.FrameLayout>
</hierarchy>
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-review-0147/e2e-mobile-app/e3-files.xml
<redacted>
<redacted>
                                          </android.widget.Button>
                                        </android.view.ViewGroup>
                                      </android.view.ViewGroup>
                                    </android.widget.ScrollView>
                                  </android.view.ViewGroup>
                                </android.view.ViewGroup>
                              </android.view.ViewGroup>
                            </android.widget.ScrollView>
                          </android.view.ViewGroup>
                        </android.view.ViewGroup>
                      </android.view.ViewGroup>
                    </android.view.ViewGroup>
                  </android.view.ViewGroup>
                </android.view.ViewGroup>
              </android.widget.FrameLayout>
            </android.widget.FrameLayout>
          </android.widget.FrameLayout>
        </android.widget.LinearLayout>
      </android.widget.FrameLayout>
    </android.widget.LinearLayout>
  </android.widget.FrameLayout>
</hierarchy>
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-review-0147/e2e-mobile-app/e3-files-portrait.xml
<redacted>
<redacted>
                                          </android.widget.Button>
                                        </android.view.ViewGroup>
                                      </android.view.ViewGroup>
                                    </android.widget.ScrollView>
                                  </android.view.ViewGroup>
                                </android.view.ViewGroup>
                              </android.view.ViewGroup>
                            </android.widget.ScrollView>
                          </android.view.ViewGroup>
                        </android.view.ViewGroup>
                      </android.view.ViewGroup>
                    </android.view.ViewGroup>
                  </android.view.ViewGroup>
                </android.view.ViewGroup>
              </android.widget.FrameLayout>
            </android.widget.FrameLayout>
          </android.widget.FrameLayout>
        </android.widget.LinearLayout>
      </android.widget.FrameLayout>
    </android.widget.LinearLayout>
  </android.widget.FrameLayout>
</hierarchy>

@iscekic
iscekic marked this pull request as draft September 9, 2026 19:30
@kilo-code-bot

kilo-code-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list-header.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list-header.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list-loading.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list-loading.test.tsx
Previous Review Summaries (2 snapshots, latest commit 8efa8a9)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 8efa8a9)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/pr-review/diff/pr-diff-file-list.tsx 138 Files-tab header and loading skeleton stay under the landscape sensor
Files Reviewed (41 files)
  • apps/mobile/app.config.ts
  • apps/mobile/plugins/withAndroidRotationSurface.js
  • apps/mobile/scripts/assert-expo-config.mjs
  • apps/mobile/src/app/(app)/(tabs)/_layout.tsx
  • apps/mobile/src/app/_layout.tsx
  • apps/mobile/src/components/agents/chat-composer.test.ts
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/session-list-content.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-content.tsx
  • apps/mobile/src/components/agents/session-list-screen.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-screen.tsx
  • apps/mobile/src/components/agents/session-list-search-header.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-search-header.tsx
  • apps/mobile/src/components/agents/session-message-list.mounted.test.tsx
  • apps/mobile/src/components/agents/session-message-list.tsx
  • apps/mobile/src/components/app-root-providers.tsx
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list.tsx - 1 issue
  • apps/mobile/src/components/pr-review/diff/pr-diff-floating-actions.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-floating-actions.tsx
  • apps/mobile/src/components/pr-review/discussion/pr-review-discussion-list.test.tsx
  • apps/mobile/src/components/pr-review/discussion/pr-review-discussion-list.tsx
  • apps/mobile/src/components/pr-review/pr-form-sheet-chrome.test.tsx
  • apps/mobile/src/components/pr-review/pr-form-sheet-chrome.tsx
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab.test.tsx
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab.tsx
  • apps/mobile/src/components/pr-review/pr-review-inbox-list.mounted.test.tsx
  • apps/mobile/src/components/pr-review/pr-review-inbox-list.tsx
  • apps/mobile/src/components/screen-header.mounted.test.tsx
  • apps/mobile/src/components/screen-header.tsx
  • apps/mobile/src/components/sheet-header-layout.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.tsx
  • apps/mobile/src/lib/pr-review/diff/pr-diff-list-bottom-padding.ts
  • apps/mobile/src/lib/pr-review/diff/use-pr-diff-list-content-padding.ts
  • apps/mobile/src/lib/tab-bar-layout.test.ts
  • apps/mobile/src/lib/tab-bar-layout.ts

Fix these issues in Kilo Cloud

Previous review (commit e823b34)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/pr-review/diff/pr-diff-file-list.tsx 138 Files-tab header and loading skeleton stay under the landscape sensor
Files Reviewed (41 files)
  • apps/mobile/app.config.ts
  • apps/mobile/plugins/withAndroidRotationSurface.js
  • apps/mobile/scripts/assert-expo-config.mjs
  • apps/mobile/src/app/(app)/(tabs)/_layout.tsx
  • apps/mobile/src/app/_layout.tsx
  • apps/mobile/src/components/agents/chat-composer.test.ts
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/session-list-content.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-content.tsx
  • apps/mobile/src/components/agents/session-list-screen.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-screen.tsx
  • apps/mobile/src/components/agents/session-list-search-header.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-search-header.tsx
  • apps/mobile/src/components/agents/session-message-list.mounted.test.tsx
  • apps/mobile/src/components/agents/session-message-list.tsx
  • apps/mobile/src/components/app-root-providers.tsx
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list.tsx - 1 issue
  • apps/mobile/src/components/pr-review/diff/pr-diff-floating-actions.test.tsx
  • apps/mobile/src/components/pr-review/diff/pr-diff-floating-actions.tsx
  • apps/mobile/src/components/pr-review/discussion/pr-review-discussion-list.test.tsx
  • apps/mobile/src/components/pr-review/discussion/pr-review-discussion-list.tsx
  • apps/mobile/src/components/pr-review/pr-form-sheet-chrome.test.tsx
  • apps/mobile/src/components/pr-review/pr-form-sheet-chrome.tsx
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab.test.tsx
  • apps/mobile/src/components/pr-review/pr-review-discussion-tab.tsx
  • apps/mobile/src/components/pr-review/pr-review-inbox-list.mounted.test.tsx
  • apps/mobile/src/components/pr-review/pr-review-inbox-list.tsx
  • apps/mobile/src/components/screen-header.mounted.test.tsx
  • apps/mobile/src/components/screen-header.tsx
  • apps/mobile/src/components/sheet-header-layout.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.tsx
  • apps/mobile/src/lib/pr-review/diff/pr-diff-list-bottom-padding.ts
  • apps/mobile/src/lib/pr-review/diff/use-pr-diff-list-content-padding.ts
  • apps/mobile/src/lib/tab-bar-layout.test.ts
  • apps/mobile/src/lib/tab-bar-layout.ts

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 62.1K · Output: 14K · Cached: 431.4K

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic force-pushed the kwf/enable-landscape-mode-in-the-93e3 branch from 2c59243 to 8efa8a9 Compare September 9, 2026 22:02
@iscekic
iscekic marked this pull request as ready for review September 9, 2026 22:14
@iscekic
iscekic marked this pull request as draft September 9, 2026 22:40
@iscekic

iscekic commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

bot: Accepted. Pushed 8eb1e80 (kwf kwf-fix-review-0147).

What changed:

  • apps/mobile/src/components/pr-review/diff/pr-diff-file-list.tsx:138 — accepted: the loaded list already padded by side insets, but the Files-tab header and loading skeleton rendered edge-to-edge under the landscape sensor housing. Fixed in pr-diff-file-list-header.tsx and pr-diff-file-list-loading.tsx: each applies left/right safe-area insets only when nonzero. The header wraps the row in an inner View so the insets add to the px-4 gutter; background and hairline stay full-width on the outer container. The skeleton matches the loaded FlashList's content-container side padding; its height-feeding bottom padding is unchanged. Zero insets collapse both styles, so portrait geometry is unchanged.

@iscekic
iscekic force-pushed the kwf/enable-landscape-mode-in-the-93e3 branch from 8eb1e80 to 060dcce Compare September 9, 2026 23:42
@iscekic
iscekic marked this pull request as ready for review September 9, 2026 23:50
@iscekic
iscekic requested a review from eshurakov September 9, 2026 23:56
@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 9, 2026
@iscekic iscekic self-assigned this Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant