Skip to content

Compatibility test: beta branch selector + launch options × Max's steam-features#3

Closed
Leb-Sun wants to merge 35 commits into
mainfrom
claude/winnative-fork-steam-merge-sbh1lx
Closed

Compatibility test: beta branch selector + launch options × Max's steam-features#3
Leb-Sun wants to merge 35 commits into
mainfrom
claude/winnative-fork-steam-merge-sbh1lx

Conversation

@Leb-Sun

@Leb-Sun Leb-Sun commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Purpose

CI compatibility check before opening the individual feature PRs. This branch merges, on top of upstream main (830008b, synced today):

  1. claude/steam-beta-branch-selector-ckipz0 (PRs feat(steam): Launch Options selector in the STEAM dropdown #1 + feat(steam): beta branch selector in the STEAM dropdown #2 — my Steam launch options + beta branch work)
  2. maxjivi05:steam-features (Max's Steam social features)

Not intended to be merged — this exists to trigger a CI build and prove the two feature sets coexist.

My features (PRs #1 / #2)

  • Launch Options selector — "Launch Options" item in the STEAM ▾ dropdown listing the game's appinfo config.launch entries (e.g. ARK's "No BattlEye"). Selection persists as shortcut extras (launch_exe_path / launch_exe_args); SteamUtils.combineSteamLaunchArgs merges the option's args with custom args across all three launch channels, honoring %command%.
  • Beta Branch selector — "Beta Branch" item opening a picker of the game's Steam branches from PICS appinfo (current one check-marked, password-protected ones locked). Selection persists and triggers the update flow so the right build downloads; steam_settings writer now reports the active branch to gbe_fork.

Max's features (steam-features)

  • Friends & presence — collapsible right-side drawer with friends grouped by status (in-game / online / offline), live persona-state updates.
  • Chat / messaging — message history, real-time incoming messages, photo attachments via Steam UGC upload, draggable chat bubbles, in-game chat overlay, notifications.
  • Achievements — trophy screen with unlock progress, icons, descriptions, dates.
  • Game joining — launch a friend's game with the connect string appended to launch arguments.
  • Steam-style launch options syntaxKEY=VALUE tokens before %command% become env vars (SteamLaunchOptions.parseEnvVars), args after become game args (SteamLaunchOptions.gameArgs).
  • 15-language localization for all of the above.

Merge conflict resolutions

Only one file conflicted: XServerDisplayActivity.java — three hunks, all the same overlap in the Steam launch-arguments pipeline (direct command line + the two ColdClientLoader.ini writers). My side wrapped exec args with combineWithSelectedLaunchArgs(...); Max's side wrapped them with appendSteamJoinConnect(SteamLaunchOptions.gameArgs(...)).

Resolution — chain the two pipelines, preserving both behaviors:

appendSteamJoinConnect(SteamLaunchOptions.gameArgs(combineWithSelectedLaunchArgs(execArgs)))
  1. combineWithSelectedLaunchArgs merges the selected launch option's args into the custom args first, keeping them attached to %command% (e.g. FOO=1 %command% -windowed + selected -NoBattlEyeFOO=1 %command% -NoBattlEye -windowed).
  2. SteamLaunchOptions.gameArgs then strips the env-var prefix and extracts the game args (-NoBattlEye -windowed).
  3. appendSteamJoinConnect appends the friend-join connect string when launching via a join.

Env-var extraction (parseEnvVars, line ~5567) still reads the raw execArgs and is unaffected, since the combine step only inserts after %command%. The localconfig.vdf / warm-stamp channel was touched only by my branch and merged untouched.

Semantic checks done on auto-merged files: both features' menu items/dialogs intact in UnifiedActivity.kt, StoreGameDetailScreen.kt, LibraryGameLaunchScreen.kt; no duplicate string resources or symbols; no references to code removed by the upstream sync; no leftover conflict markers.

Test plan

  • CI build passes (this is the point of the PR)
  • Launch a Steam game with custom args + a selected launch option → both arg sets present
  • KEY=VALUE %command% syntax still sets env vars
  • Join a friend's game → connect string appended after option/custom args
  • Beta Branch + Launch Options items both appear in STEAM ▾ alongside friends/achievements UI

https://claude.ai/code/session_0122eiViExS97baaDytDSYJq


Generated by Claude Code

maxjivi05 and others added 30 commits June 5, 2026 22:12
…oin game

Steam Friends:
- Right swipe-out drawer (Compose M3) mirroring the left drawer, with
  self status header, avatars, and In-Game/Online/Offline grouping.
- Live friend presence: logon sends protocol_version, ChangeStatus with
  persona_set_by_user/need_persona_response, and subscribes via
  Chat.RequestFriendPersonaStates so Steam pushes stateful persona updates.
- Wire-type-aware CMsgClientPersonaState parser (field 25 arrives as a
  fixed64 in stateful pushes); has_persona_state/has_game guards keep
  metadata-only responses from clobbering live state, game name, and app id.
- In-game friends show a compact card with a text-height game-art capsule
  and the resolved game title beside it (title from the local app DB, then
  the public store API, since Steam omits game_name for Steam apps).
- The redundant self-status section was removed from the left filter drawer.

Steam Messages:
- FriendMessages protobufs (SendMessage, GetRecentMessages, IncomingMessage)
  plus JNI for send, history, and a drainable incoming queue fed by the
  FriendMessagesClient.IncomingMessage notification.
- Compose M3 chat screen with history, real-time incoming, optimistic send
  dedup, and avatars.
- Image attachments: system photo picker -> Steam chat UGC upload
  (beginfileupload/PUT/commitfileupload with a minted web access token,
  file_type=MIME) delivered to the friend. Renders [img], [img src=...],
  and bare images.steamusercontent.com/ugc image URLs.

Steam Achievements:
- Trophy button on the game launch screen between Settings and Create
  Shortcut, opening a Compose M3 achievements screen with icons, names,
  descriptions, X/Y progress, and unlock dates.
- Overlay the user's real unlock state from CMsgClientGetUserStatsResponse
  achievement blocks onto the schema-derived definitions.

Join Game:
- Join button on joinable in-game friends launches their game (when owned
  and installed) with the connect string appended to the Steam launch args
  (ColdClient ExeCommandLine and the Goldberg arg path).

Launch options:
- Steam-style %command% launch options: KEY=VALUE tokens before %command%
  become environment variables for the game process; arguments after
  %command% are passed to the game. Added a "Steam (%command%)" preset.

Localization:
- All new user-facing strings extracted to resources and translated across
  all 15 supported locales (da, de, es, fr, hi, it, ko, pl, pt-BR, ro, ru,
  uk, zh-CN, zh-TW, plus English).
…oin game

Steam Friends:
- Right swipe-out drawer (Compose M3) mirroring the left drawer, with
  self status header, avatars, and In-Game/Online/Offline grouping.
- Live friend presence: logon sends protocol_version, ChangeStatus with
  persona_set_by_user/need_persona_response, and subscribes via
  Chat.RequestFriendPersonaStates so Steam pushes stateful persona updates.
- Wire-type-aware CMsgClientPersonaState parser (field 25 arrives as a
  fixed64 in stateful pushes); has_persona_state/has_game guards keep
  metadata-only responses from clobbering live state, game name, and app id.
- In-game friends show a compact card with a text-height game-art capsule
  and the resolved game title beside it (title from the local app DB, then
  the public store API, since Steam omits game_name for Steam apps).
- The redundant self-status section was removed from the left filter drawer.

Steam Messages:
- FriendMessages protobufs (SendMessage, GetRecentMessages, IncomingMessage)
  plus JNI for send, history, and a drainable incoming queue fed by the
  FriendMessagesClient.IncomingMessage notification.
- Compose M3 chat screen with history, real-time incoming, optimistic send
  dedup, and avatars.
- Image attachments: system photo picker -> Steam chat UGC upload
  (beginfileupload/PUT/commitfileupload with a minted web access token,
  file_type=MIME) delivered to the friend. Renders [img], [img src=...],
  and bare images.steamusercontent.com/ugc image URLs.

Steam Achievements:
- Trophy button on the game launch screen between Settings and Create
  Shortcut, opening a Compose M3 achievements screen with icons, names,
  descriptions, X/Y progress, and unlock dates.
- Overlay the user's real unlock state from CMsgClientGetUserStatsResponse
  achievement blocks onto the schema-derived definitions.

Join Game:
- Join button on joinable in-game friends launches their game (when owned
  and installed) with the connect string appended to the Steam launch args
  (ColdClient ExeCommandLine and the Goldberg arg path).

Launch options:
- Steam-style %command% launch options: KEY=VALUE tokens before %command%
  become environment variables for the game process; arguments after
  %command% are passed to the game. Added a "Steam (%command%)" preset.

Localization:
- All new user-facing strings extracted to resources and translated across
  all 15 supported locales (da, de, es, fr, hi, it, ko, pl, pt-BR, ro, ru,
  uk, zh-CN, zh-TW, plus English).
Settings gear in the friends drawer self-status row opens a Chat Settings
dialog: Chat Notifications, Chat Heads, Auto-Hide, and Enable Chat in Game.

- Global incoming-message pipeline in SteamService: a single poller drains
  messages and re-publishes via a SharedFlow plus per-friend unread counts,
  consumed by the chat screen, system notifications, and the chat-head overlay.
- System notifications (high-importance channel) for incoming friend messages.
- Messenger-style chat heads: a draggable system-overlay bubble that snaps to
  any screen edge, fades when idle (Auto-Hide), shows an unread badge, and
  un-hides on a new message; tapping pops a panel beside the head with the full
  friends list (game-art badges) and a conversation view (text + image send via
  a transparent picker proxy). Works over games when Enable Chat in Game is on.
- In-game gating via GameSessionState; orientation-aware re-snapping.
Adds a "Launch Options" item to the STEAM menu on the game detail
screen that lists the game's appinfo config.launch entries (e.g.
ARK's "Launch ARK (No BattlEye)") with a checkmark on the current
choice. Selecting one persists as the game's default:

- exe -> shortcut extra launch_exe_path + container.executablePath
  (the priority resolveRelativeGameExe already honors; non-default
  exes ride the existing WN_STEAM_DIRECT_EXE override)
- the option's own arguments -> new shortcut extra launch_exe_args,
  kept separate from the user's custom execArgs

LaunchInfo gains an `arguments` field (defaulted, so cached appinfo
JSON stays decodable) parsed from appinfo config/launch/arguments.

A shared SteamUtils.combineSteamLaunchArgs joins option args with
custom args across all three launch channels (direct command line,
ColdClientLoader.ini ExeCommandLine, localconfig.vdf LaunchOptions /
GetLaunchCommandLine). It honors Steam's %command% placeholder in
custom args. The steam-env warm stamp now includes the effective
LaunchOptions line so a changed selection re-runs the localconfig
edit, and warm launches re-push the per-process launch command line.

The submenu reuses the existing StoreSourceActionPopup surface and
StoreSourceMenuItem styling; hidden unless the game is installed and
has 2+ distinct options.

https://claude.ai/code/session_01EUsZxzAWCjSh8LYRS9W3vT
The v1 selector only existed on the store detail screen
(StoreGameDetailScreen); tapping an installed game in the library
grid opens LibraryGameDetailDialog -> LibraryGameLaunchScreen, which
has its own duplicated STEAM dropdown - so the feature was invisible
from the library. Mirror the submenu there (same two-page popup,
using this screen's Launch* styling) and wire it in
LibraryGameDetailDialog for Steam games only.

Also fix two latent issues that could hide or weaken the selector:

- Cached appinfo rows predate LaunchInfo.arguments, so options that
  differ only by args deduped down to one entry and fell under the
  2-option visibility gate. Dedup now includes the option label, and
  both dialogs do a two-phase load: show cached options immediately,
  then SteamService.refreshAppInfoFromPics (new public wrapper around
  fetchLatestSteamAppInfo + persistLatestSteamAppInfo) re-fetches the
  app's PICS appinfo and reloads, healing upgraded installs on first
  open.

- Extracted the option-list builder and selection persistence into
  shared UnifiedActivity helpers used by both screens, replacing
  GameManagerDialog's inline copies.

https://claude.ai/code/session_01EUsZxzAWCjSh8LYRS9W3vT
The in-popup submenu rendered as an awkward full-width overlay. Replace
it with a centered modal window that follows the Workshop window's
design language: dimmed scrim, 560dp-max rounded Surface, header with
an accent icon badge + LAUNCH OPTIONS overline + game title + count
chip + close button, and a divided row list. Rows show the option label
with its executable/arguments as the secondary line; tapping persists
the selection and moves the check mark.

- New LaunchOptionsScreen.kt mirroring WorkshopScreen's palette and
  layout (all vector icons; no new drawable assets needed).
- Both STEAM dropdowns return to a flat menu whose "Launch Options"
  item opens the window (showLaunchOptions/onLaunchOptions params
  replace the list plumbing through the SourceTags).
- LaunchOptionsDialog host added next to WorkshopDialog in both
  GameManagerDialog and LibraryGameDetailDialog.

https://claude.ai/code/session_01EUsZxzAWCjSh8LYRS9W3vT
Findings from a full-branch review applied:

- PICS appinfo re-fetch now runs once per app per process (retried on
  failure) instead of on every game-detail open, and the two-phase
  load + refresh logic is shared by both dialogs via
  loadSteamLaunchOptionsRefreshing — drops a network round-trip and a
  duplicate shortcut scan from repeat opens.
- LaunchOptionsDialog owns selection persistence (appId +
  onSelectionSaved), removing the duplicated persist lambda from both
  host sites.
- XServerDisplayActivity: single combineWithSelectedLaunchArgs
  resolver replaces the getExtra+combine pattern repeated across the
  direct-launch and both ColdClient INI sites.
- Steam-env stamp stores the raw effective LaunchOptions line instead
  of its hashCode — exact equality, no silent collision skips.
- StoreLaunchOptionItem moved to LaunchOptionsScreen.kt where the
  picker lives.

https://claude.ai/code/session_01EUsZxzAWCjSh8LYRS9W3vT
Adds a "Beta Branch" menu item to the STEAM ▾ dropdown on both
game detail screens (StoreGameDetailScreen / LibraryGameLaunchScreen).
Tapping it opens a Workshop-styled picker (BetaBranchScreen.kt)
listing the game's PICS branches with the current one check-marked.
Password-protected branches are shown disabled (no beta-password
flow exists in this app).

• BetaBranchScreen.kt — new Workshop-shaped modal; mirrors the
  structure of LaunchOptionsScreen.kt with AltRoute icon and per-file
  palette constants.

• Menu item hidden when the game has only one branch (public),
  mirroring the >= 2 gate used by Launch Options.

• Persistence: SteamService.setSelectedBetaBranch() stores the choice
  as the "selectedBranch" shortcut extra (blank = public/default);
  SteamService.resolveSelectedBetaName() refactored to reuse
  findSteamShortcut() instead of its own loadShortcuts() scan.

• Downstream already honors the selection (resolveDepotManifestInfo,
  setAppCurrentBeta, ACF buildId); after a successful save the dialog
  calls startUpdateCheck() so the new branch's build downloads.

• SteamUtils.writeCompleteSettingsDir: configs.app.ini [app::general]
  now writes is_beta_branch/branch_name from the actual selected branch
  instead of always hardcoding public, so gbe_fork's
  GetCurrentBetaName() / GetAppBuildId() return correct values.
  branches.json already lists all branches correctly — no change needed.

• Appinfo refresh guard generalised: launchOptionsRefreshedApps
  renamed to appinfoRefreshedApps so one PICS round-trip per process
  feeds both the Launch Options and Beta Branch pickers.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
startUpdateCheck was calling checkForAppUpdate(appId) without a branch,
so the comparison always ran against the public manifests and reported
"no updates found" even after switching branches.

The private downloadApp overload (routing downloadAppForUpdate) also
hardcoded branch = "public", so the download itself would have fetched
the wrong manifests even if the check had passed.

Fix: resolve resolveSelectedBetaName before the update check and pass
it to checkForAppUpdate; resolve it again inside the private downloadApp
overload instead of hardcoding "public". Fresh installs (no shortcut
yet) resolve to "" → "public" unchanged.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
…ownload hot paths

Clicking install/check-update crashed after the branch-selector fix:
resolveSelectedBetaName ran ContainerManager().loadShortcuts(), whose
Shortcut constructor decodes full cover-art bitmaps for every shortcut,
runs PE icon extraction over game exes and can rewrite .desktop files.
Doing that on every download/update click spikes the heap (OOM lands on
whatever thread allocates next — runCatching can't save the app) and
races the UI's own .desktop reads.

resolveSelectedBetaName now parses the [Extra Data] section of the
.desktop files directly — no Shortcut construction, no bitmap decode,
no writes. The heavyweight findSteamShortcut path remains only in the
one-off setter (setSelectedBetaBranch).

Also:
• checkForAppUpdate / isUpdatePending default their branch parameter to
  the game's selected beta, so every caller (startUpdateCheck, the
  store dialog's onDownloadUpdate) is branch-aware without per-site
  resolution; the UnifiedActivity call site is back to one line.
• startUpdateCheck wraps downloadAppForUpdate in runCatching — a failed
  update start now shows the failure popup instead of crashing.
• completeAppDownload prunes stale "{depotId}_{gid}.manifest" caches
  not matching depot.config — branch switches no longer accumulate old
  manifests, and checkForAppUpdate's cache fallback can't mistake a
  previously-downloaded branch for the installed one. Legacy installs
  without depot.config are left untouched.
• BetaBranchScreen: remember() the formatted date; ktlint-safe wrapping.

Known limitation (documented, native-side): the Rust depot writer does
not delete game files absent from the new branch's manifest, so files
removed between branches linger until a fresh install. Fixing that needs
a manifest-diff pass in wn-steam-client.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
Applied from a seven-angle review pass (line-scan, removed-behavior,
cross-file tracing, reuse, simplification, efficiency, altitude):

• "public" comparisons now case-insensitive (equals ignoreCase),
  matching the existing convention in branches.json / manifest
  resolution — covers the row label, sort order, selection fallback,
  blank-mapping on save, and configs.app.ini is_beta_branch.
• Branch picker closes after a successful save so the update-check
  popup it triggers is what the user sees next, not a stale window.
• Install/DLC size estimates in GameManagerDialog are sized against
  the selected branch (resolved once per load) — previously the cards
  showed public-branch sizes while the download fetched the beta.
• readSelectedBranchExtra uses FileUtils.readLines (same reader as
  Shortcut's parser): one corrupt .desktop file no longer aborts the
  scan of remaining containers.
• Stale-manifest prune extracted to pruneStaleDepotManifestCache next
  to the other cleanup helpers.
• Store onInstall download start wrapped in runCatching — an exception
  there killed the app (plain coroutine launch, no handler).
• Dropped the redundant pwdRequired guard in the picker row callback;
  the row-level clickable gate already enforces it.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
getSelectedLaunchOption ran ContainerManager().loadShortcuts() on every
game-detail open. Each Shortcut constructed that way decodes the full
cover-art bitmap, runs PE icon extraction over the game exe and can
rewrite .desktop files — a large transient heap spike and a write race
against the UI's own shortcut reads, just to fetch two strings.

New readSteamShortcutExtras parses the [Extra Data] section straight
from the .desktop files and returns the owning container dir; the
container.executablePath fallback is read from the .container config
JSON directly. The heavyweight findSteamShortcut path remains only in
setSelectedLaunchOption, which must materialize the shortcut to write.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
The Steam store tab crashed on game tap while the library detail
worked. GameManagerDialog runs three data-loading LaunchedEffects the
library dialog doesn't all share — and an exception inside a
LaunchedEffect tears down the whole Activity.

All data-loading effects in GameManagerDialog and the shared
launch-options/beta-branches loader effects now degrade on failure
(logged, menu items hidden / zero sizes) instead of crashing, with
CancellationException rethrown so effect restarts still work.

Also dedupes the merged shortcut readers: resolveSelectedBetaName now
uses readSteamShortcutExtras from the launch-options branch (which
gained the same lightweight .desktop reader for getSelectedLaunchOption)
and the selectedBranch-specific copy is gone. Read and write paths now
agree on "first STEAM shortcut for the appId".

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
…een signature

The store-tab crash was never a logic bug: ART's bytecode verifier
rejected StoreGameDetailScreenKt itself —

  VerifyError: [0x17BC] wide register v5 has type Low-half
  Constant/Zero/null

a D8/Compose codegen defect triggered by the composable's huge
parameter list (≈44 params incl. several longs + default masks). The
launch-options build, two parameters smaller, verified fine on the
same device; adding showBetaBranches/onBetaBranches pushed the
generated default-argument handler over the edge. The class failed to
load the moment the store dialog composed it — which is why it crashed
instantly, for every game, store tab only, and no runCatching could
help.

Fix: fold each show-flag + callback pair into a single nullable
callback (null = menu item hidden) in StoreGameDetailScreen,
LibraryGameLaunchScreen and their source-tag menus. That returns
StoreGameDetailScreen to exactly the parameter count that demonstrably
passes the verifier, with a slightly cleaner API.

Also fixes the PR CI compile failure: the merge had interleaved a
duplicate com.winlator.cmod.shared.io.FileUtils import in
SteamService.kt ("Conflicting import: name is ambiguous").

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
…rifier

Second VerifyError from the same device, different instruction:

  [0x1749] copy-cat1 v0<-v260 type=Reference: WindowInsets

v260 is the tell: with the 40+-param defaulted signature and the whole
screen body compiled into one method, the frame needed 260+ registers,
and at that pressure D8's prologue emits register copies this device's
ART verifier rejects. Reshaping parameters (previous commit) just moved
the failure — the giant shared frame is the trigger.

Fix splits the two pressure sources into separate methods:
• StoreGameDetailScreen keeps the exact public defaulted signature but
  its body is now only "pack args into StoreGameDetailParams, call
  content" — a small frame containing the default-mask prologue.
• StoreGameDetailContent(p) holds the original body behind local
  rebinds (body text unchanged) — a big body but a one-param method
  with no default machinery, so the frame stays well under the
  high-register regime.

Call sites are untouched; behavior is identical.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
Debloat from the crash investigation, now that the real cause
(VerifyError, fixed by the wrapper/content split) is known:

• Reverted the speculative runCatching guards added while hunting
  blind — GameManagerDialog's install-data load, the DLC size effect
  and the onInstall download call are back to the unguarded house
  style they had on main. Kept the two intentional guards: the
  launch-options/beta loader effects (network-dependent PICS refresh;
  failure should hide menu items, not kill the dialog) and
  startUpdateCheck's downloadAppForUpdate (our feature's primary flow).
• Extracted refreshAppInfoFromPicsOnce — the once-per-process PICS
  refresh guard previously duplicated inside both loader functions.
• Trimmed stale verifier-saga comments from the nullable menu-callback
  params; the full story lives once, on StoreGameDetailParams where
  the split it explains actually is.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
…ility testing

Combines the Steam beta-branch selector + launch-options selector
(PRs #1/#2) with Max's steam-features branch (friends/presence drawer,
chat, achievements, game joining, Steam-style launch option syntax),
on top of upstream main 830008b.

Conflicts resolved in XServerDisplayActivity.java (3 hunks, all in the
Steam launch-args pipeline): both sides are now chained as
appendSteamJoinConnect(SteamLaunchOptions.gameArgs(combineWithSelectedLaunchArgs(execArgs)))
so the per-game selected launch option's args are merged into the
custom args first (preserving %command% placement), then Max's parser
strips the env-var prefix and extracts game args, then a friend-join
connect string is appended. Env-var extraction (parseEnvVars) still
reads the raw execArgs, which is unaffected by the combine step.

https://claude.ai/code/session_0122eiViExS97baaDytDSYJq
Pre-install, the store screen's Download pill now has a small segment
"sliced off" its left edge (AltRoute icon, 48dp — min touch target,
mirrored 14dp/4dp corners with the house 8dp gap) that opens the
existing beta-branch picker. The pick is STAGED in memory only
(pendingBetaBranch); size cards retarget to the staged branch
immediately, and nothing persists or downloads until the Download
button commits it.

• StoreCtaButton gains defaulted modifier/shape params; the glass
  brushes are extracted into storeCtaGlassBrushes shared with the new
  StoreCtaCutoutButton. Zero new StoreGameDetailScreen params — the
  existing nullable onBetaBranches drives the cutout pre-install and
  the STEAM-menu item post-install, so Epic/GOG (null) never show it.
• GameManagerDialog loads beta branches for both install states (after
  the install state resolves, so the cutout can't flash on installed
  games); launch options stay install-gated.
• BetaBranchesDialog is now a dumb shell with a single onSelect — the
  installed hosts (GameManager + library) persist and start the update
  check exactly as before; the pre-install host just stages and closes.
• Download commit: persists a custom install path BEFORE the branch
  write (setSelectedBetaBranch creates the shortcut, which snapshots
  game_install_path and is never rewritten — committing in the other
  order would freeze the default path and break launch from custom
  dirs), then downloads; pause/resume re-resolves the branch from the
  now-persisted shortcut. If the shortcut can't be created yet (setup
  incomplete) the install proceeds on public with the existing
  beta-branch-failed toast.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
claude added 5 commits June 11, 2026 18:09
Reinstalling WinNative wipes app-private shortcuts (where the
selectedBranch extra lives) while game files on external storage
survive — so a reinstalled app showed a beta install as "public", and
worse, a tapped update check would diff the beta files against public
manifests and silently downgrade the game.

Steam survives this because it persists the beta key WITH the install
(appmanifest ACF, UserConfig/betakey). Our durable analogs live in the
surviving game dir, used in order of trust by the new
recoverSelectedBetaName(appId):

1. .DepotDownloader/depot.config — the installed manifest gids matched
   exactly against each beta's PICS manifests (evidence of what the
   files actually are; requires all overlapping depots to match and at
   least one gid to differ from public, single candidate only);
2. steam_settings/configs.app.ini branch_name — the selection recorded
   at the game's last launch (checked at the game-dir root and next to
   the exe, the two settings-dir locations the launch path writes).

A successful inference heals the shortcut via setSelectedBetaBranch, so
every downstream consumer is consistent again. Wired at the three
chokepoints: the branch-picker loader (UI), checkForAppUpdate /
isUpdatePending (branch param now nullable, null = recover-and-resolve)
and prepareLibSteamClientForLaunch (game launch). Known limitation: a
never-launched install whose beta has since published a new build
matches neither source and stays public until re-picked.

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
…ecovery

From a seven-angle review of the two newest commits:

• Branch-name casing: an ini-recovered name is now canonicalized to the
  PICS branches-map key before healing — downstream buildId lookups
  (app.branches[name]) are exact-key and would silently miss an
  ini-cased variant.
• Recovery is memoized per process (betaRecoveryAttemptedApps): it can
  only succeed right after a WinNative reinstall, so permanently-public
  games no longer rerun the full inference (DB read + depot.config
  parse + ini probes) on every dialog open, update check and launch.
• readBranchNameFromSettingsIni derives the exe dir from the SteamApp
  already in hand instead of getWindowsLaunchInfos' redundant blocking
  DB fetch of the same row.
• pendingBetaBranch is cleared when a game resolves to installed — a
  staged pre-install pick was either committed by Download or
  abandoned, and could otherwise leak into a later session of the
  dialog. The host's selectedBranch expression simplifies to
  pending ?: persisted on that invariant.
• The branch cutout now shares the Download button's enabled gate so
  users can't stage picks an actively-blocked download won't honor.
• The 20-line commit block in onInstall is extracted to
  commitPendingBetaBranch, mirroring the persist helper next to it.

Refuted during verification, for the record: cutout-without-betas (the
host gates onBetaBranches on branches.size >= 2), cross-game state
leaks (remember(app.id) keying), setSelectedBetaBranch false-success
(it returns false on a null shortcut), customPath double-application
(idempotent by construction), and the lost explicit-public API
(callers can pass requestedBranch = "public").

https://claude.ai/code/session_01EJFSZYUPkR1Cwer6CC2cNa
The beta-branch cutout and the Download segment each painted the full
teal->purple gradient, so it repeated. Each segment now paints only its
slice of a single gradient spanning the whole row, matching the look of
the undivided Download button.

https://claude.ai/code/session_013E3pTpGg4C6ropUPA9QyNB
@Leb-Sun Leb-Sun closed this Jun 18, 2026
@Leb-Sun Leb-Sun deleted the claude/winnative-fork-steam-merge-sbh1lx branch June 18, 2026 11:38
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.

3 participants