Integrate Fiber State Management and Float Ranks#399
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 48bc033. Configure here.
Step7750
left a comment
There was a problem hiding this comment.
Apologies for the late review!
The fiber parsing is quite nifty, thanks for figuring all that out -- certainly seems required with the lack of context when injecting.
I'm thinking we can potentially move this PR to be more declarative instead of injecting a wrapper that then finds a child element to manually inject into. This would require expanding the InjectionGuard to a degree, but IMO would enable us to directly configure injecting the rank element.
| private placeNextToWear(): void { | ||
| if (this.injected || !this.itemInfo || !this.card) return; | ||
| if (!parseRank(this.itemInfo)) return; | ||
|
|
||
| const wearSpan = this.findWearSpan(); | ||
| if (!wearSpan) return; | ||
|
|
||
| this.injected = true; | ||
| wearSpan.insertAdjacentElement('afterend', this); | ||
| } | ||
|
|
||
| private findWearSpan(): HTMLSpanElement | null { | ||
| if (!this.targetFloat) return null; | ||
|
|
||
| const spans = this.card.querySelectorAll<HTMLSpanElement>('span[style*="pre-wrap"]'); | ||
| for (const span of spans) { | ||
| const text = span.textContent?.trim(); | ||
| if (!text) continue; | ||
| const value = parseFloat(text); | ||
| if (!Number.isNaN(value) && Math.abs(value - this.targetFloat) < 1e-6) return span; | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Totally understandable that it is getting quite difficult to figure out which element relates to wear considering we can't rely on DOM siblings being consistent (i.e. some skins have elements like stickers).
However, we're starting to break the pattern of declarative handling (granted, I think some usages of the old market UI also did).
I'm wondering if we can find a middle-ground where InjectionGuard inside of @Inject* can provide some of this context filtering that can't easily be encoded into a query selector. For instance, we'd have a @InjectAppend decorator for BetaListingRank that attempts to target the exact element we want to append to. Since we expect our query selector to collide, we can pass the proposed element into the InjectionGuard and do some context handling to deduce whether a float is there.
As an alternative, to make our lives simpler, we could try to inject into a more stable element. However, you'd lose the flair of having the rank next to the float.
There was a problem hiding this comment.
Agreed, the imperative handling isn't ideal. I do believe using ReactListingEnhancer as orchestrator is the simplest and most stable approach though.
Without the context from ReactListingEnhancer (i.e. knowing the float we look for), the guard could only search for "a parseable number between 0 and 1", which becomes extremely brittle (e.g. a skin may have an applied name tag with a matching number).
This approach will quickly hit its limits, for example for our paint seed enhancements in #401 ("any number between 0 and 1000") as it wouldn't be able to distinguish between the pattern template and paint seed.
We also have to pass down the fetched listing props and itemInfo from ReactListingEnhancer (here: low_rank, high_rank) as all smaller injections depend on it. Fetching them directly wouldn't be possible.
I would leave the final choice up to you, but the declarative approach would definitely come with much stricter limits along the way, which might not be easily solvable.
|
|
||
| /** Steam implementation detail: the "key" property on the Fiber node is the listing ID. */ | ||
| get fiberProps(): MarketListingProps | null { | ||
| return getFiberProps<MarketListingProps>(this.card, (fiber) => typeof fiber.key === 'string') ?? null; |
There was a problem hiding this comment.
nit: I'd expect this to be more stable if we checked that memoizedProps contains listing as a field. Valve could add a ancestor element that is closer with some props that breaks this parsing more easily IMO.
There was a problem hiding this comment.
Not quite. The way this traversal works is that we traverse through the original React layers until we reach the base component. From my analysis, their code looks something like this:
function ListingCard({ listing, ... }) {
return (
<{/* Some memo/forwardRef wrapper */}>
<Grid ...>
<Focusable {/* React.context wrapper for controller / Steam Deck */} ...>
<div ...> /* ← this is our entry HTML traversal element */
{/* inner content */}
</div>
</Focusable>
</Grid>
</{wrapper}>
);
}So we basically traverse the non-rendered layers up until we reach the base of the React component. And we know this one will always contain key as it's the unique identifier of the list rendering. Something like this:
function ListingResults({ listings }) {
return (
<>
{listings.map((listing) => (
<ListingCard
key={listing.listingid} // ← this is fiber.key
listing={listing}
...
/>
))}
</>
);
}Technically, we could switch to detecting listing within memoizedProps, it shouldn't really matter at all though as there cannot be any React components in our traversal up before key is defined. On the other hand, if Valve decides to rename the listing props later on, we would traverse up until the top root in the worst case, which would make the website pretty laggy for users until we push a fix. Using key we are guaranteed to stop at the list-rendering React component.

Closes #398
Step 2 of #395
Adds state management integration with Fiber and first Fiber-based listing enhancement: float ranks. More enhancements to follow separately.
Screenshot
Note
Medium Risk
Relies on undocumented React Fiber/DOM structure and brittle card selectors that may break when Steam updates the beta UI; changes are gated to the beta market only.
Overview
Adds React Steam Market (beta) support by reading listing data from React Fiber instead of legacy
g_rgListingInfo/g_rgAssets.A new
getFiberPropshelper walks the__reactFiber$tree to pull typedMarketListingprops (listing id, asset, inspect link, wear from asset properties).BetaListingEnhancercontinuously injects into beta listing cards whenisReactSteamMarket()is true, fetches inspectItemInfovia the existing float fetcher, and mountsBetaListingRankbeside the wear value span (matched by float text). The market listing page script now loads this enhancer alongside the legacyItemRowWrapperpath.Only a trivial newline fix in
mode.ts; legacy market behavior is unchanged.Reviewed by Cursor Bugbot for commit 48bc033. Configure here.