Skip to content

Steam games in the library, and Settings grouped by platform - #18

Draft
Bium23 wants to merge 27 commits into
mainfrom
bium/library-manager
Draft

Bium23 wants to merge 27 commits into
mainfrom
bium/library-manager

Conversation

@Bium23

@Bium23 Bium23 commented Nov 14, 2026

Copy link
Copy Markdown
Owner

Status: awaiting human validation. Nothing here is approved for release until the maintainer has tried it in NX Library Dev.

What this adds

NX Library can now show native Mac Steam games next to Switch ports. Settings are grouped so each platform's settings are separate.

Steam games

  • Steam libraries appear as a source, scanned without changing game files.
  • Steam games use the same head, hero and Spotlight page as Switch games. Logo and buttons now arrive with the same motion.
  • Art:
    • squares use the app's own icon;
    • automatic backgrounds prefer screen-shaped art from Xbox, GOG, TheGamesDB and LaunchBox fan art, then a screenshot, then the library hero;
    • LaunchBox also finds PC art.
  • Play starts real Steam sessions:
    • sessions survive an NX Library restart;
    • a session follows the process it saw start.
  • Steam Options replace the separate Steam page.
  • Keep Steam in the background (Steam · Library → Play): Steam starts -silent, stays hidden while the game starts and comes back if it needs the player.

Settings

  • Sidebar groups: General, Switch (Library, Games, Engine), Steam (Library), System (Storage, About). Search and Changed name platform pages, for example "Steam · Library · …".
  • Switch-only rows moved into the Switch group:
    • Check engine at launch and Setup → Engine;
    • Refresh library at launch and Box covers → Library.
  • Readiness and Environment are removed: Engine's Compatibility panel already shows and re-checks the same things.
  • Three things stay where they were, on purpose:
    • Rumble stays in Controllers, with clearer copy;
    • App icon stays in Look (it is the Dock icon);
    • Storage stays whole.
  • The sidebar follows the selection without animation.
  • Search opens its field only on A, Return or Right.
  • Esc in the field closes only the field.

Fixes

  • The moonlight backdrop keeps 60 fps on ProMotion while idle. It now uses CAMetalDisplayLink on macOS 14+; CVDisplayLink was dropping it to about 30 fps after about 10 s without input.

Cleanup

  • The unused SteamBackdrop view and its UI-less check are removed. CI now runs the shared game header check instead.
  • Orphan strings are removed.

Checks run

  • swift test: 1386 tests pass.
  • check_localizations.py, check_publication.py, run_notices.py and run_game_header.py pass.
  • The app was built with scripts/build_app.sh, and codesign --verify --deep --strict passes.

Awaiting manual validation by the maintainer

  • Steam background launch: Steam hidden at start, and it comes back when it needs the player.
  • Steam and Switch head arrival look the same.
  • LaunchBox automatic background. Clear Settings → Storage → Artwork cache to recompute it.
  • Settings phase 2 layout, keyboard and controller navigation, Reset group, all six languages.

Do not merge before the maintainer confirms.

🤖 Generated with Claude Code

Bium23 and others added 27 commits November 13, 2026 17:54
…tation)

Catalog repository and migration, Dev profile, Steam discovery,
visibility and metadata, and the first half of the shared game
experience plan (common models, GameLogoArea, GamePrimaryActions).
Rebased on the published 0.1.4.3 (nxnative 8a5c5e7e).

Known gap: SteamArtworkRequest is referenced by its test but not
implemented yet, so the test target does not compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SteamArtworkRequest picks the layout role's local art: the player's pick
first, then for squares the bundle's CFBundleIconFile (kept inside
Contents/Resources). Spotlight, search and the Steam details square ask
for the square role; Collection keeps the portrait cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spotlight and Collection show a Steam game with the same logo area,
action positions, stats line, genre chips and arrival motion as an
nxnative game. Until Steam Play and Options exist, the first two
positions open Details and hide the game. The Steam hero now loads
through SeleneHeroArt, so it crossfades like nxnative art.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spotlight recorded a Steam selection as a non-game, so moving between
Steam and nxnative games dropped the art to the wallpaper until the next
hero loaded. Collection cleared it the same way. Both now keep the art
until the next game's hero crossfades in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Spotlight page now takes a source's presentation and actions:
Steam games get the same screenshot strip and viewer, About this game,
activity column and action grid as nxnative games, with an Installation
panel instead of Build & Sources. GameDetailPresentation gains a Steam
initializer that claims nothing nxnative. Steam facts are shared by the
head, the page and the input counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steam's library hero is a ~3:1 banner, so filling the screen enlarged and
cropped it. SteamBackdropChoice now picks the first screen-shaped image:
the stores' clean heroes found by name (Xbox, GOG Galaxy, TheGamesDB fan
art), then the game's first Steam screenshot, the banner last. The
player's own pick still comes first, and the choice is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Play hands steam://run/<AppID> to the verified Steam client only. A
session starts when the installation's own app is seen running and
ends when it exits; Steam staying open never counts. The Steam head
shows Play, Resume, Cancel while waiting and Try again after a
failure, with a one-line note; Spotlight and Collection A and hints
follow it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Steam game's Options pill, Y in Spotlight and Y in Collection open
Options in Game options' look and input: Play, Edit details (Change
artwork, Change metadata, Store page), Move on Spotlight, Favorite,
Hidden and Show in Finder. The shared artwork and metadata pickers now
work on a Steam game: Steam's own art for its AppID, online finds and
SteamGridDB browsing, saved in the Steam personal store, never touching
the installed app's icon. The separate Steam details page and its sheet
editor are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Steam game already running when NX Library starts now gets its
session: the coordinator reads no processes before it has the session
store. Stopping it forgets old states, Play reads the processes before
resuming, and an empty store answer is asked again after a minute.
Architecture notes describe the shared Steam experience.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change artwork lists a Steam game's own art (labelled Steam library)
before the slower online search finishes, and shows wide banners
whole. Browse online on LaunchBox looks for a Steam game's Mac or
Windows entry, never a console one. Play hands the request to Steam
without bringing Steam to the front.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Valve has no way to keep an open Steam out of sight when it gets
steam://run, so Play now starts a closed Steam with the documented
-silent -nofriendsui options and hides an open one while the game is
on its way. After 15 s without the game Steam is shown again and the
head says to check it; once Play is over Steam comes back only if it
was in view before. The setting lives in Game sources and is on by
default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidebar now lists sections under General, Switch, Steam and
System. Game sources becomes Steam's Library page, with Keep Steam in
the background under Play, and Switch's Library no longer links to it.
Search, Changed and the page title name a platform section with its
platform ("Steam · Library"), and section order, sidebar navigation and
the settings catalog follow the groups. Section identities are
unchanged. Settings subheadings are now translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the group headings the sections are taller than the window, and
the sidebar never scrolled to the selection, so About stayed out of
view. The sidebar now scrolls the selected section into view when it
appears and whenever the selection changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The animated scroll started in the same frame as the new section's
content and stuttered, most visibly when the selection wraps from
About back to Search. The sidebar now brings the selection into view
without animation, as macOS sidebars do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting Search in the sidebar focused its field at once, so the keys
went to the field instead of the sidebar. The field now opens only on A,
Return or Right from the sidebar (or a click); Down from the field still
goes to its results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Esc (or B) while typing in Search resigned the field and then also went
back, leaving Settings. Back now only closes the field when typing, and
the player stays on Search in the sidebar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The moonlight was paced by a CVDisplayLink, which tells the system
nothing about its frame rate. After about ten seconds without input a
variable-refresh display slowed down, nextDrawable waited up to 80 ms
and the motes fell from 60 to ~30 fps; typing or any other on-screen
change hid it. Measured with a temporary frame log: Settings Search
stuttered after 12 s idle, Start-up too.

On macOS 14 and later the moonlight now runs from a CAMetalDisplayLink
asking for 60 fps, on a render thread of its own: the display keeps its
rate and each drawable comes when the screen can take it, and the
motes advance by presentation time. The same log then read 57-61 fps
idle with no stalls. macOS 13 keeps the CVDisplayLink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Spotlight a Steam head slid as one block, while a Switch head
slides only its logo and fades the pills and stats in after it; in
Collection its fade eased instead of running linear. The Steam head now
uses the Switch heads' own motions, shared as constants. Steam logos
are kept decoded like Switch ones, so a game seen before shows its
logo from the first frame instead of its title, and a logo that lands
later fades in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The automatic Steam background now tries LaunchBox's fan art for the
game's Mac or Windows entry after Xbox, GOG and TheGamesDB and before
screenshots: the full file, and only one whose stated size suits a
screen. Steam's 3:1 library banner stays the last resort, shown
cropped. With no banner, the first image that loads is shown instead
of the last URL tried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A running Steam session was checked against the installation as the
catalog has it now. With the installation gone (a refresh) any process
with the same pid kept it open, and with the app named at a new path
the session ended while the game was still playing. The session now
lasts while the process it saw start, same pid and same bundle, is
alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check engine at launch and Setup move to Switch · Engine; Refresh
library at launch and Box covers to Switch · Library. Readiness and
Environment go: Engine's Compatibility already shows and re-checks the
same environment. The caches stay only in Storage. Rumble, App icon
and Storage keep their places, with the reasons in architecture.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SteamBackdrop had no callers; its UI-less check goes with it and CI
runs the shared game header check instead. Twelve strings with no use
left in the sources are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
appendingPathComponent asked the disk, so a missing folder had no
trailing slash and no longer equalled file.deletingLastPathComponent().
CI, on a fresh Mac, failed testSharedAndScopedDefaultPathsAreConsistent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steam's hero, logo and tiles used NSImage(contentsOf:), which decodes
on first draw on the main thread. Measured on a cached 1920x1080 Steam
hero: 56-75 ms for that first draw against 14-16 ms predecoded, the
hitch seen going from a Switch game to a Steam one. They now use
GameArtLoader.decode at the nxnative sizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The column keeps its quicker 0.12 s delay and 0.3 s length; its fade
now eases out like Spotlight's logo instead of running linear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
steamBundleID is a constant read from NSWorkspace observers, so it is
nonisolated; SteamMetadataService's default transport is a @sendable
closure. steamPersonalizationChanged was never posted or observed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- refresh no longer asks LaunchServices isTerminated for every running
  app, and launches/quits arrive once (runningApplications KVO), not
  also through the workspace notifications.
- A pid's paths are resolved once while it lives and an installation's
  once per check, instead of every process x game x refresh.
- Steam tiles read the app icon once per bundle instead of in body.
- supportedOperations/requireSupported/CatalogGameOperation and
  CatalogGameOptions.specificRows were never used by the app; the real
  guard is the record payload (wrongIntegration), now what tests check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant