fix: replace the wordmark's SVG mask with plain filled paths - #1185
Merged
Conversation
Reported live: on mobile Safari the header logo's 'W' rendered as a solid near-white rectangle in dark theme, while 'bJs' (plain filled paths, no mask) rendered fine. Confirmed the committed source, the Railway origin, and the Cloudflare-served asset are byte-identical, so this is not the separate Cloudflare staleness issue, it is a genuine rendering bug in the asset technique. All four affected files (both lockup variants, both mark variants) built the 'W' via an SVG <mask>: a stroked zigzag painted white inside a black rect, with a further black rect punching a horizontal slice through the middle. An <img> that starts display:none (exactly what hidden dark:block produces for the two theme variants) and later becomes visible is a known WebKit failure mode for masks: the browser can skip reapplying the mask on first paint and show the raw unmasked base rect instead, which for the dark variant is #f7f5f2, matching the reported white box exactly. Replace the mask in all four files with two plain filled <path> elements (the miter-stroke outline of the same zigzag, clipped against the same horizontal band via Sutherland-Hodgman), the same technique 'bJs' already uses and which never exhibits this bug. Verified pixel-identical to the mask rendering for all four files: 0 differing pixels at 3% fuzz (antialiasing only) against a neutral background. webjs-monogram.svg and webjs-monogram-on-light.svg are unaffected: they cut the slice by painting a rect in the tile's own solid background color, which needs no mask since that tile's background is known and solid, unlike the wordmark/mark files sitting on arbitrary page content. Rebuilt webjs-brand-assets.zip so the download bundle matches. Closes #1184
vivek7405
force-pushed
the
fix/wordmark-svg-mask-safari
branch
from
July 30, 2026 09:12
af824b4 to
7640ab8
Compare
vivek7405
marked this pull request as ready for review
July 30, 2026 09:19
This was referenced Jul 30, 2026
vivek7405
added a commit
that referenced
this pull request
Jul 30, 2026
* ci: purge the Cloudflare cache after each site deploy webjs.dev's static assets are served with max-age=14400 at stable urls, so after a deploy the edge keeps serving the previous copy for up to four hours. That shipped two visible regressions in one day (a pre-redesign tailwind.css after #1179, then the un-fixed logo marks after #1185), each cleared by a manual dashboard purge somebody had to remember. Staleness is also per-asset rather than all-or-nothing, so the site can sit half-updated with no signal. The workflow deliberately does not purge on push. Railway auto-deploys from the same push and the build takes minutes, so an immediate purge would evict the cache while the origin still served the old bytes and the next visitor would repopulate the edge with exactly those bytes, leaving the site stale for another four hours and burning the run for nothing. Instead it polls /__webjs/version (#239) on the Railway origin, bypassing the cache it is about to purge, until the reported uptime is lower than the elapsed time since the run began, which proves the restart belongs to THIS push rather than some earlier deploy. On timeout it fails loudly instead of purging. Purge is zone-wide: all four hostnames are proxied in the one webjs.dev zone, so a single call covers them and cannot silently miss an asset the way a hand-maintained path list does. curl uses --fail-with-body and the response is asserted on .success, since a bare curl exits 0 on a 4xx and would report a rejected purge as a success. workflow_dispatch is wired for on-demand purges, which is what the two incidents above actually needed. Closes #1188 * ci: harden the purge workflow against transient origin failures Self-review of the new workflow turned up four real defects, all in the wait step's failure handling. 1. Under `set -e`, `uptime=$(... | jq ...)` aborts the whole step when jq fails, so a single non-JSON body from the origin (exactly what a mid-deploy error page returns) turned a transient blip into a hard failure instead of another retry. Every extraction now ends in `|| true`, and the numeric comparison defaults to false. 2. The loop was 60 iterations of (10s curl + 15s sleep), a 25 minute worst case against `timeout-minutes: 25`, so the job-level kill could fire before the step printed why it gave up. The loop is now deadline-driven on an explicit 15 minute WAIT_BUDGET, which holds whatever the per-request latency turns out to be, leaving 10 minutes of headroom for the diagnostic. 3. The verify step reported "Edge matches the origin" when EITHER side sent no ETag, and Cloudflare strips ETags under some zone settings, so the reassuring message was reachable with no comparison behind it. A missing header is now reported as inconclusive. 4. The job declared no `permissions`, inheriting the default token scopes despite never checking out the repo or calling the GitHub API. Now `permissions: {}`. Verified by running the real wait step against stub origins: the happy path (uptime below elapsed) exits 0 after detecting the restart, and a dead origin exits 1 with the refuse-to-purge guidance.
This was referenced Jul 30, 2026
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
webjs-lockup-on-{dark,light}.svg,webjs-mark-on-{dark,light}.svg) built the "W" via an SVG<mask>. An<img>toggled fromdisplay:noneto visible (exactly whathidden dark:blockdoes for the two theme variants) is a known WebKit failure mode for masks, showing the raw unmasked base rect instead.<path>data (same technique "bJs" already uses, never affected), computed as the miter-stroke outline of the same zigzag clipped against the same horizontal band.Test plan
compare -metric AE, neutral background to expose any transparency difference too)/brandwebjs checkcleannpm test(unit + browser), 35/35 browser tests passwebjs-brand-assets.zip; the existing zip-freshness test intest/ssr/brand-page.test.tspassesCloses #1184