A card only shows its map once location data has landed, so the bundled
placeholder maps were never meant to be seen, and their labels sat in one
language among many. An empty map URL now renders a same-sized muted box,
which also covers a payload with a country but no coordinates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any non-empty language reached Static Maps, so every distinct value was an
uncached upstream call whose answer the edge kept for a year. A tag-shape
check still passes region-tagged UI locales (zh-TW, pt-BR) without an
allow-list, and the handler block now covers both sides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UI passes its locale code as the map language, and pt-BR and zh-TW are
registered locales, but /api/map only accepted a bare two-letter code, so
every map request from those two locales answered 400 and the map never
loaded. Pass the tag through like every other ?lang (api/AGENTS.md) and
URL-encode it where the Static Maps URL is built, so the value stays one
query parameter.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
runChain's optional originalSite / accept move into an options object, so a
chain that needs only one no longer passes an `undefined` placeholder.
The per-version validators become isValidIPv4 / isValidIPv6 in
common/valid-ip.js, next to isValidIP: the "Valid" prefix keeps them apart
from the non-validating isIPv6 discriminator.
Tests now run the four single-version entry points against a stubbed fetch,
so dropping or swapping a chain's validator fails the suite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proxy-Seller joins the paid sponsors in all six READMEs and the footer
acknowledgement list. Each sponsor's bold name in the description line now
links to the same destination as its logo, so the copy is clickable too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README listed every sponsor as an equal-size logo at the bottom, with
the only ad copy hidden in a hover-only title attribute. Paid sponsors now
get their own group with a visible one-line description under each logo;
free-plan supporters share one row of smaller logos. V.PS links carry the
README's locale in utm_content.
SwiftProxy joins the paid group and the footer acknowledgement list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Score details is one of the most-opened panels, but the score is not a
live reading. A line under it links to Blocklist Check with the IP
carried as `?q=`, which the tool now prefills without running: the
visitor may be signed out or out of quota.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home and ToolPage are both kept alive with a Footer each, so once a tool
page had been visited, a footer link on Home opened both sheets and the
cached one, stuck on its own tab, covered the right one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A detail request still in flight when the overview reloads (Refresh, or
the re-pull on activation) wrote its answer back after the reload had
cleared the cache, so the reopened card skipped its fetch and showed the
older snapshot. Reload bumps an epoch; an answer from an earlier one is
discarded.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
Tool pages end their breadcrumb on Advanced Tools, as the <h1> beside it
names the tool. The crumb was rendered as the current page (aria-current,
not clickable) and its section ignored; now any crumb with a section links
to it, and only a bare last crumb is the current page.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The tool page is kept alive, so a visitor returning after the backend's
5-minute refresh saw the status pulled at mount until they hit Refresh.
On activation, an overview older than that cycle reloads as Refresh does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
UserMenu raises the request from a tool, privacy or report page and goes
Home first. Home's Achievements is an async component: on a first visit it
can mount after the flag is already set, so the request was missed and the
flag stayed set, swallowing every later one. The watcher now runs
immediately and acts only on a raised flag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
WebKit records a page's back/forward snapshot as it navigates away,
so a tool or section picked from the mobile nav sheet left a snapshot
with the sheet still sliding out, which a swipe back then showed. The
sheet closes first and the navigation follows its slide-out.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
useRoute() inside the computed ran inject() outside setup, which
returns undefined: Footer threw on every render and the navigation
back to the homepage lost its scroll restore (the page landed at the
top). The route is resolved once in setup.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
VITE_FIREBASE_AUTH_DOMAIN is each environment's own host, served by the
/__/auth proxy; the PWA-only VITE_FIREBASE_PWA_AUTH_DOMAIN and
utils/auth-domain.js go. Every OAuth provider lists the handler URL per
environment (Google and GitHub both take several redirect URIs).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
GitHub OAuth apps now take several redirect URIs, with optional
wildcard matching, so the PWA's own auth domain can be registered
there like Google's; the menu no longer hides GitHub when the PWA
signs in through a different domain (signInProvidersFor and the
store getter go). The operator notes name both registrations.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
nav.Title / PingTest / MTRTest / RuleTest had no reader in any locale.
The store takes isFireBaseSet from firebase-init.js instead of
recomputing it in checkFirebaseEnv(). The --page-bg comment no longer
claims index.html's colours must match it; they are tuned separately.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
A pass over every file the branch touched, after the iOS animation hunt
left tries and reverts behind it. Removed what nothing reaches: the
view-transition rule for a transition no code starts, an unreachable
guard in the transition hook, a no-op KeepAlive key branch, a sign-in
guard the menu already enforces, usage-dialog wrappers that only
forwarded to the store, a duplicated modifier-click check and card
filter (isPlainClick / listedTools), a test that hand-rolled
listedTools, a drawer-era fixture parameter. auth-flow.js folded into
auth-redirect.js. Comments cut to what the code cannot say itself —
file headers of a few lines, the measured iOS reasons, non-obvious
orderings — and stale ones fixed (Nav under KeepAlive, Home.vue
reusing DEFAULT_META, "from Nav", enabledCards). AGENTS.md sections
tightened to the code as it is now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
On iOS, accelerated CSS animations running before a swipe-back stay
visually frozen once the gesture lands on the previous page: the frame
rate, the animations' own time and their iteration events all carry on,
but the compositor paints no new frame for them until their layer is
rebuilt (measured with the ?debug=nav overlay; cancel + play, a
display:none round trip or a re-insert revive them, ancestor style
changes do not). A push revives them as a side effect of its page
transition; a history step, where the browser's gesture replaces our
transition, does not. After every navigation that came from popstate,
utils/revive-animations.js cancels, replays and resets to its time each
running CSS animation, two frames after the DOM lands and again at
400ms and 1200ms in case the gesture snapshot is dismissed late.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The Nav's CSS animations (Pulse's globe and ping) stall on iOS after a
swipe-back into the homepage, with KeepAlive and the page transitions
both ruled out and no WebKit available off the device. ?debug=nav
(kept in localStorage, ?debug=off clears) mounts widgets/NavDebug.vue:
live rAF rate, whether the ping animation's time advances and iterates,
reference CSS animations in a fixed panel and in the page flow, the
Nav's hidden state, a timeline of popstate (with hasUAVisualTransition)
/ route / visibility / focus / touch / scroll events, buttons that try
one candidate remedy at a time, and a Copy button for the log. Removed
once the cause is known.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
Route changes animate like native navigation: a tool page slides in
from the right, going home slides it back out, tool to tool cross-fades
— utils/page-transition.js decides the direction from the route pair
and wraps the router's DOM update in document.startViewTransition from
router/index.js, so the snapshot, the swap and the scroll restoration
happen before the animation and pages of different heights never
overlap. Nothing animates on a query-only change, under reduced motion,
on a history step the browser already animated (swipe-back), or in a
browser without the API; a navigation arriving mid-transition skips the
running one. The Nav opts out by view-transition-name and stays put
while the page moves.
Reapplied after the Nav's animation stall on iOS proved unrelated: it
reproduces with the transitions removed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
Since iOS 26 the system draws its Liquid Glass scroll-edge blur over
the top of a Home Screen web app whose page runs under the status bar,
which black-translucent asked for, and the blur covered the Nav. With
no apple-mobile-web-app-status-bar-style the status bar stays opaque
system chrome above the web view; the strip Nav.vue painted under a
translucent one goes with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
Reverts the View Transitions hook-up, its keyframes, the Nav naming
and the two diagnostic follow-ups. On iOS the Nav's own animations
(Pulse's globe and ping) stalled after the forward transition and
stayed stalled after a swipe-back, while a transition back home
restored them — the capture of the fixed Nav is the suspect, and the
clean way to confirm it on the device is to run without any of it.
This reverts commits 2a360f0, d6d80da, 38a61fd, 156d0bf and 856f965.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
iOS 26+ draws its scroll-edge blur over the top of a Home Screen web app
unless a hit test at the top edge finds a fixed, viewport-wide box with
a plain background colour (WebKit's LocalFrameView::fixedContainerEdges,
which honours pointer-events on its first pass). The safe-area strip is
exactly that box; it no longer opts out of hit testing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
`?vt=off` turns the View Transitions off, `?vt=unnamed` keeps them but
leaves the Nav un-named (it slides with the page), `?vt=default`
restores the normal behaviour; the choice lives in localStorage. WebKit
cannot run in the sandbox and the Nav's animations stall on iOS after
a transition, so this is how the trigger gets bisected on the device.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
WebKit leaves the accelerated CSS animations inside an element captured
under its own view-transition-name stalled once the transition is over:
after the first page transition Pulse's spinning globe and ping only
repainted in half-second jumps (measured frame by frame on iOS 26), and
a swipe back brought the stalled Nav home. Every transition now ends by
cancelling and replaying the running animations in the named elements
(restartAnimations in utils/page-transition.js), which recreates them
on the compositor.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
VITE_FIREBASE_AUTH_DOMAIN stays the shared sign-in domain every browser
uses; the new optional VITE_FIREBASE_PWA_AUTH_DOMAIN is the domain the
installed PWA initialises Firebase with — the site's own host, served
by the /__/auth proxy, so the redirect handler runs inside the PWA
(utils/auth-domain.js). GitHub's OAuth app has one callback, on the
shared domain, so when the PWA domain differs the account menu offers
Google only. .env.example documents both and the console-side steps.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
A view-transition-name makes an element a captured, separately
rendered group, and WebKit kept the Nav's own animations (Pulse's
spinning globe and ping) janky after an iOS swipe-back with the name
always on. The Nav and the status-bar tint now carry their names only
under html[data-page-transition], which the router hook sets before
the snapshot and clears when the transition finishes, so outside one
the Nav is an ordinary fixed element again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco