Skip to content

Storefront and crate-preview defects (#95, #96) - #97

Merged
CaYatur merged 2 commits into
mainfrom
fix/store-ui
Jul 28, 2026
Merged

Storefront and crate-preview defects (#95, #96)#97
CaYatur merged 2 commits into
mainfrom
fix/store-ui

Conversation

@CaYatur

@CaYatur CaYatur commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Six defects reported from a live install. Detail and reasoning are in the two issues and the commit message; the short version:

# Where What
1 both storefronts the crate SVG had no intrinsic size, so any unstyled host rendered it at 300x150
2 storefront detail \Opens with: spin\ leaked an internal animation id to buyers
3 public site Buy while signed out opened the login behind the product
4 public site a bare OK button at the bottom of the page — leftovers of the crate modal that \CRATE_MODAL_HTML\ replaced
5 desktop app the crate preview played behind the editor that launched it (80 vs the app's 90)
6 desktop app the store-wide animation picker under the currency, redundant since every crate has its own

The one worth reading is #1: the fix is \width/\height\ on the element, not another CSS rule. An inline SVG with only a viewBox has no intrinsic size, so the failure mode of a missing rule is a 300x150px glyph rather than a slightly wrong one. It was sized in .sf-badge\ and nowhere else, which is exactly how it shipped.

#4 is worth reading for a different reason: the duplicate markup included two closing </div>\ tags that close nothing, so the page's structure had been quietly wrong since the crate modal was extracted into shared code.

Verify

\MSMS_SMOKE_WEB\ renders both storefronts from the shared source and asserts every one of these, including that each of #crateOk, #crateResult, .crate-modal\ and #sfModal\ appears exactly once in the served page. All twelve gates exit 0.

Closes #95. Closes #96.

Six reported from a live install, all in what a buyer or an operator actually
looks at.

The crate glyph carried only a viewBox. An inline SVG with no width/height has
no intrinsic size, so any host that does not happen to style it renders the
replaced-element default — 300x150px. It was sized in .sf-badge and nowhere
else, which is why the same icon was 14px on a card and enormous in the section
heading and the panel's product list. The size is now on the element, so
forgetting a rule is a wrong size rather than a broken page; the heading drops
the glyph entirely, since it sat next to a label that already says "Crates".

The detail view announced "Opens with: spin" — the internal id of a transition
the buyer is about to watch anyway. Gone.

Clicking Buy while signed out opened the login *underneath* the product: both
overlays are fixed, and the detail (75) outranked the auth modal (70). buy()
now closes the detail first, and the auth layer moved above it as the rule that
holds if some later path forgets.

The public page carried a bare OK button at the bottom. The shared crate modal
had been pasted in beside the leftovers of the hand-written one it replaced,
leaving a duplicate #crateOk and #crateResult in the page body along with two
closing tags that close nothing.

In the desktop app the crate preview played behind the editor that launched it:
the shared overlay is 80, the app's own modal backdrop is 90. The overlay now
sits at 95 — above every host's modal layer, below the toast layer, so an error
about a purchase stays readable over the animation announcing it.

And the store-wide animation picker under the currency is gone. Every crate
chooses its own where the person deciding is already looking at that crate; a
control setting the fallback for a field nobody leaves unset is one to get
wrong. The stored value stays as what "inherit" resolves to.

MSMS_SMOKE_WEB now renders both storefronts and asserts: every inline svg has
its own size, the heading has no glyph, the detail does not leak the animation
id, buying signed-out closes the product and opens the login, the shared
overlay outranks the desktop modal layer, and each of #crateOk, #crateResult,
.crate-modal and #sfModal appears exactly once in the served page.

All twelve gates green.
Copilot AI review requested due to automatic review settings July 28, 2026 12:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

sfRender redraws the grid and never touches SF.detail, so an open product
kept the object from the previous load - and since #82 that is where stock
and per-player counts are read. A refused purchase reloaded the catalogue,
updated the grid behind the modal to 'sold out', and left the modal saying
one left with Buy enabled.

sfSetProducts now replaces the catalogue for both pages: it re-resolves an
open detail against the new list and re-renders it, or closes it when the
product is gone entirely, since re-rendering something no longer for sale
would be worse than saying it is gone.

Also drops the store.plays key from both site locales and the panel table -
the line that rendered it is gone, and a dead key is the same class of
leftover as the orphaned crateOk markup this branch deletes.
@CaYatur

CaYatur commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

Self-review

An open detail survives a catalogue reload pointing at the previous load.

Both pages did the same thing:

SF.products = r.body.products || []
sfRender()

sfRender() redraws the grid. It does not touch SF.detail, which still holds
the product object from the previous load. Since #82 put stock and per-player
counts in the detail, that is exactly where the stale numbers show:

  1. A buyer opens a crate's detail. Stock says 1.
  2. Someone else takes the last one.
  3. They press Buy → 409 out-of-stockloadStore() → the grid behind the
    modal updates to "Sold out", the modal keeps saying 1 left with Buy enabled.
  4. They press Buy again. Same failure.

This is the same shape as the bug reported in #95 — an overlay showing something
that is no longer true — reached from the other direction, and I found it while
checking whether buy()'s new sfCloseDetail() was enough.

Fixed with sfSetProducts(list) in the shared storefront, called by both pages:
it re-resolves an open detail against the new catalogue and re-renders it, or
closes it if the product is gone entirely. Re-rendering something that is no
longer for sale would be worse than saying it is gone.

Asserted: reload with the detail open and the product now stock: 0 → the
reopened detail's Buy is disabled; reload without the product at all → the
detail closes.

Also removed: the store.plays key from both site locales and the panel's
table. The line that used it is gone, and a dead key nobody renders is the same
class of leftover as the orphaned #crateOk markup this PR deletes — it just
takes longer to notice.

All twelve gates green after the fix.

@CaYatur
CaYatur merged commit 9b02ad7 into main Jul 28, 2026
@CaYatur
CaYatur deleted the fix/store-ui branch July 28, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants