feat(inventory): SkinCraft 3D viewer embed - #410
Open
ykns3 wants to merge 17 commits into
Open
Conversation
…SFloat First step toward embedding the SkinCraft 3D viewer in the extension (mirroring the phoenix partner-iframe integration). Adds a "View in 3D" button to the right of "List on CSFloat" in the inventory selected-item pane, laid out side-by-side via a new .market-btn-row. Styled after SkinCraft's classic-theme primary button (the viewer's top-island "Inspect" action): brand indigo fill with the white logo mark so it stands out beside the muted List button. Shares the List gating via a new canListOnCSFloat getter; the click handler is a no-op stub until the persistent-iframe embed is wired up.
Applies the review conventions from phoenix#1669 to the extension embed. - drop the export-for-test inventory-click helper and its spec - collapse the duplicate frame-reveal methods into showFrame() - share one target builder, one Steam image helper, and named inspect/hex-colour patterns instead of repeated literals - move the shared target types beside the protocol so the service no longer imports a type from a component - split the modal CSS into skincraft_viewer_modal_styles.ts - fold isSellableOnCSFloat into canListOnCSFloat and derive the brand icon from environment.skincraft_embed_origin - point the dev embed origin at localhost
Replaces the createElement tree and the manual classList toggling with a lit-html template, keeping the imperative shell the modal needs. A FloatElement is not an option here: the content script builds this modal and Chrome leaves `customElements` null in isolated worlds, so no custom element can be defined in that context. `render()` only needs the DOM. - classMap/styleMap replace hand-built class strings and style.setProperty - createRef/ref replace the stored dialog and iframe handles - guard() around the inventory grid so progress ticks don't re-diff every card - both status blocks stay mounted and toggle `hidden`, so the item icon survives an error -> retry cycle (its fade-in needs the <img> to already exist) The iframe stays unconditional with a static src, so it is created once and only diffed after that, and the embed still boots exactly once. The service's view of the modal is unchanged.
Matches phoenix's WebGpuAvailabilityService: probe with requestAdapter() rather than trusting `navigator.gpu` to exist, since a present API still fails on outdated drivers or without hardware acceleration. The status is three-state and pessimistic while checking, so the button never appears and then fails. Clients with no `navigator.gpu` at all resolve to unavailable synchronously — that covers Firefox before 141, and the manifest supports 127+. Those users simply see no 3D button, rather than sitting through the 20s embed load timeout. Adapted to this codebase: a module singleton instead of a signals service, with the probe memoised behind settled() and started on first use so no GPU work happens at import time in the content-script world, where nothing renders.
ykns3
marked this pull request as ready for review
July 28, 2026 21:55
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 587fd98. Configure here.
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
Adds a View in 3D action to Steam inventory items, opening the SkinCraft viewer in a modal over the page. The viewer is a persistent, sandboxed cross-origin iframe driven entirely over
postMessage— the extension never re-srcs it, so switching items inside the modal is instant once warm.How it works
gSkinCraftEmbedis the single source of truth for the boot/load lifecycle: a versioned protocol (origin-pinned in both directions, never'*'), load correlation ids so staleprogress/loaded/errorevents can't clobber a newer load, a 20s load timeout, andpause/resumeon tab visibility.g_ActiveInventoryand posts the clicked item plus a bounded inventory snapshot to the content-script instance, which owns the iframe. The iframe deliberately stays out of the page world, so Steam's own scripts can't reach it, its message channel, or its closed shadow root.asset_properties[propertyid=6]— SkinCraft resolves it client-side, so there's no Steam GC round-trip and no dependency on a float fetch.SkinCraftViewerModalowns one<dialog>created once and never unmounted, since unmounting would reload the embed. It renders withlit-html'srender()rather than extendingFloatElement: the content script builds it, and Chrome leavescustomElementsnull in isolated worlds, so no custom element can be defined there.guard()so load-progress ticks don't re-diff every card.webgpu_availability.ts): adapter-only probe, so the button renders only where the viewer can actually run. Three-state and pessimistic while probing, so it never appears and then fails.Sandbox / security
sandbox="allow-scripts allow-same-origin allow-downloads",allow="fullscreen",referrerpolicy="no-referrer". Inbound messages are gated onevent.originand shape-validated at both boundaries (skincraft_embed_protocol.ts,skincraft_viewer_protocol.ts); outbound is pinned to the embed origin. The inventory snapshot is length-bounded and every field is checked before it crosses.Behaviour notes
navigator.gpuisn't Baseline — absent before Firefox 141, platform-dependent after — against astrict_min_versionof 127.Dev notes
skincraft_embed_originat a local SkinCraft dev server. The prod embed allowlist has no localhost entries, andsteamcommunity.comstill needs adding as an allowed ancestor before this works outside dev — that SkinCraft deploy ships alongside this. Without either, the modal shows its error state; nothing else is affected.Rollout & testing
npm run build,npm run checkformat,npm test(30 tests / 5 files), plustsc --noEmitandmadge --circular(345 files, no cycles). Node 24, matching CI.master@f882f20, unchanged).Note
Medium Risk
Cross-origin iframe messaging and sandbox boundaries are security-sensitive, but origins and payloads are validated; main rollout risk is dependency on SkinCraft embed allowlisting and WebGPU availability on user browsers.
Overview
Adds a View in 3D flow on Steam inventory item details that opens SkinCraft in an in-page modal, gated on WebGPU and valid masked inspect hex from Steam asset properties (no float fetch required to open).
skincraft_embed_originis added to all environment configs.gSkinCraftEmbedowns a long-lived sandboxed iframe and a versioned, origin-pinnedpostMessageprotocol (load ids, 20s timeout, pause/resume on tab hide). The page world posts an open message plus a bounded inventory snapshot; the content script hosts the modal and iframe so Steam page scripts cannot reach the embed.SkinCraftViewerModalis lit-html (not a custom element) with a closed shadow root, persistent iframesrc, loading/error UI, and an optional inventory strip (seed/float from float cache, rarity styling).selected_item_infoshows the new button beside refactored List on CSFloat in a flex row; listing rules are unchanged but extracted intocanListOnCSFloat.float_fetcher.getCachedsupports inventory card metadata.Tests cover embed and viewer message validation, progress handling, and inventory target extraction.
Reviewed by Cursor Bugbot for commit 0afeb89. Bugbot is set up for automated code reviews on this repo. Configure here.