feat(nova): open game detail in its own window instead of a bottom sheet - #187
Merged
Conversation
Opening a game raised a BottomSheetDialogFragment of just over 3,000 lines that mixed sheet plumbing with the Compose content it hosted. This gives the detail its own Activity and splits the file along the seam that was already there. The Compose half moves verbatim into NovaGameDetailContent.kt so the ordering the source guards assert is unchanged. The fragment half becomes NovaGameDetailActivity, which keeps the constructor lambdas the sheet was built with as properties of the same name and shape, so the 500-line body that used them needed context and lifecycle substitutions rather than a rewrite. The window returns the launch it chose rather than performing it, so the stream starts from the library after the window is gone. Two payloads travel back, mirroring the two paths the sheet already had: artwork edits merge into the library list, and the launched game goes to the launch. Passing only an id and looking the game back up would have dropped the MangoHUD choice made in that window. PolarisGame is @serializable, so the game travels as JSON rather than through a hand-written mirror of PolarisGameJsonAdapter that could drift from it. The serialization runtime is already on the APK's runtime classpath via the shared model; this only puts it on the app's compile classpath. expandBottomSheet sized whatever the fragment's root view was, which was right for the detail sheet and incidental for the desktop-Steam dialog that also called it. That dialog is the only sheet the window still hosts, so the helper now sizes the content view it is handed. Behaviour is otherwise unchanged; the cinematic restructure is separate.
papi-ux
force-pushed
the
nova/game-detail-window
branch
from
August 5, 2026 21:31
55743c8 to
d1f7e16
Compare
15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Opening a game raised
NovaGameDetailSheet, aBottomSheetDialogFragmentof just over 3,000 lines that mixed sheet plumbing with the Compose content it hosted. This gives the detail its own window and splits the file along the seam that was already there.NovaGameDetailContent.kt, so the ordering the source guards assert is unchangedNovaGameDetailActivity, which keeps the constructor lambdas the sheet was built with as properties of the same name and nullable shape. The 500-line body that used them needed context and lifecycle substitutions rather than a rewrite, so what ships is the code that was reviewed as a sheetTwo payloads travel back, mirroring the two paths the sheet already had: artwork edits merge into the library list, and the launched game goes to the launch. An earlier draft passed only a game id and looked it back up in
allGames, which silently dropped the MangoHUD choice made in that window —launchGamereadsgame.mangohud.PolarisGameis@Serializable, so the game travels as JSON rather than through a hand-written mirror ofPolarisGameJsonAdapterthat could drift from it. The serialization runtime is already on the APK's runtime classpath via:shared:polaris:model; the added dependency only puts it on the app's compile classpath.expandBottomSheetsized whatever the fragment's root view was, which was right for the detail sheet and incidental for the desktop-Steam dialog that also called it. That dialog is the only sheet the window still hosts, so the helper now sizes the content view it is handed.Behaviour is otherwise unchanged. The cinematic restructure is separate.
Guard tests
The four suites that string-match the detail source now read both halves, Activity first, joined — Kotlin sources end in a newline, so no symbol can straddle the join and every ordering assertion still compares symbols within the same half. Where a guard's subject genuinely moved it was re-pointed, not relaxed:
viewLifecycleOwnerLiveDatalifecycle.currentStateonGameUpdatedblockonGameDetailResultNovaGameDetailSheetComposeTestis renamedNovaGameDetailComposeTest. Three stalelint-baseline.xmlentries for the deleted file are removed, and the one warning that moved with the code (UseKtxonSharedPreferences.edit) is fixed rather than re-baselined.Exact candidate
d1f7e162753dc381dfe70bcc9a30ebb2acb8ff76c7a4b353Verification
Run against a clean build of the pushed tree:
:app:testNonRoot_gameDebugUnitTest— 1,170 tests, 0 failures/errors:app:lintNonRoot_gameDebug -PlintFailOnError=true— CI's exact invocation, no errors:app:assembleNonRoot_gameDebug:app:assembleNonRoot_gameRelease:app:assembleNonRoot_gameDebugAndroidTestnonRoot_gameDebugflavor, class-filtered —NovaGameDetailComposeTest, 0 failurestopResumedActivity = NovaGameDetailActivity; B returns to the libraryonLaunch game=ARC Raiders mirror=false virtual=false→result game=ARC Raiders id=1BECC52E-… appId=1937920881, flags preserved,com.papi.nova.Gamestarted. Diagnostics removed before commitForce-push, and why
The first push of this branch was an incomplete tree: it carried the four new files but none of the modifications or deletions, so
NovaGameDetailSheet.ktstill existed alongsideNovaGameDetailContent.ktand the serialization dependency was missing. CI caught it; my local runs did not, because my working tree was correct and only the commit was wrong.The cause was a
git stash/git stash poprun between staging and committing while checking the manifest's line endings.stash poprestores to the working tree but not the index, which silently unstaged everything. The commit has been amended with the complete tree, the staged deletions verified explicitly before committing, and the whole suite re-run against it.Note for review
During the first hands-on launch I saw one launch resolve to a different game than the window had open. I could not reproduce it across two instrumented runs, both of which show the contract carrying the right game and flags. Two host-side conditions applied at the time and not afterwards: the game in question is not installed on the host — Steam answers the launch with a 51 GB install prompt — and there was a resumable session for the other game, which the Moonlight protocol prefers over starting a new app. The launch-decision code in
onPrimaryLaunchis byte-identical to the sheet's. I am flagging it rather than filing it as fixed, because I have an explanation for the session but not for the toast text I saw.The
mirrorDesktopbranch of the desktop-Steam decision has not been exercised on hardware; the re-runs haddesktopSteamActive: false.