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
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 and the iOS status-bar tint opt out by
view-transition-name and stay put while the page moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The installed iOS PWA opens an out-of-scope authDomain in the system's
in-app browser view, whose storage the PWA's own webview cannot read,
so a redirect sign-in completed there never reached the app. The
frontend server and the Vite dev server now proxy /__/auth/* to
https://<project>.firebaseapp.com (common/firebase-auth-proxy.js),
mounted only when VITE_FIREBASE_PROJECT_ID is set, so with
VITE_FIREBASE_AUTH_DOMAIN set to the site's host the handler runs
in-scope, inside the PWA itself. .env.example documents the three keys
and the console-side requirements (authorized domain, OAuth callback at
https://<host>/__/auth/handler).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The w / d / s / c keys scrolled a section 80px below the viewport top
while the nav links used 70px, so the same section landed at two
different spots. SECTION_SCROLL_OFFSET now lives in utils/scroll-to.js
and every section scroll — nav, breadcrumb, keyboard — uses it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
An iOS standalone PWA has no popup with a usable opener, so
signInWithPopup never delivered its result there: the provider sent
the visitor back to the homepage and the app saw no sign-in. The store
now picks the flow (utils/auth-flow.js): popup in a browser tab, as
before; signInWithRedirect in the installed PWA, and as the fallback
when a popup is blocked. A redirect marks its provider in sessionStorage
(utils/auth-redirect.js) and boot consumes the marker before first
render with getRedirectResult, so the first authenticatedFetch carries
the token like the signed-in boot path. The toast host now shows an
alert raised before mount, which a failed redirect return is.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The homepage sections and the advanced tools are peers, so every route
shows the same Nav, rendered once from App.vue alongside the User and
Preferences hosts. What differs is the click: on the homepage a section
link scrolls; elsewhere it navigates home and then scrolls, re-aiming
while the freshly mounted homepage still resizes; an advanced tool
entry opens /tools/<slug> from anywhere (utils/nav-target.js +
composables/use-nav-target.js). The slim page header, its tools menu
and the body padding switch it needed are gone; open sheets close on a
route change.
Tool pages, /privacy and /r/:id carry a breadcrumb above their title
(PageBreadcrumb.vue): Home steps back in history when the homepage is
the previous entry, Advanced Tools goes home to the cards, the current
page is the last crumb. Cards use an arrow instead of the drawer icon.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
Every way into a tool — the homepage cards, the nav dropdown and mobile
sheet, the keyboard shortcuts, ASN Info's profile link, the banners —
navigates to /tools/<slug>; the bottom drawer, its full-screen toggle
and the ?tool= query it was driven by are gone from Advanced.vue. The
pages stay alive (App.vue / ToolPage.vue), so browser back and forward
move between tools and the homepage, each restored as it was left.
Legacy /?tool=<slug> links redirect to the page in a router beforeEach
(utils/legacy-tool-link.js: known slug → its page with the remaining
query, unknown → the homepage). Persona Check drops noStandalone: with
the homepage kept alive its dependency commands stay registered, so the
page dispatches them in place and only a direct visit navigates home
first. The four sign-in tools no longer close a drawer before opening
the usage dialog, which PageHeader's User hosts.
Changelog entries in the six full locales; frontend/AGENTS.md describes
the page model.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The pages outside the homepage (/tools/:slug, /privacy, /r/:id) get one
PageHeader: a back button that steps back in history when the previous
entry is the homepage (restoring the kept-alive page and its scroll) and
pushes / otherwise (utils/back-target.js), the brand, a tools menu
grouped by category (ToolsMenu.vue, over listedTools() in
utils/tool-availability.js) so a tool can be switched without going
home, the current page's title, and the account menu. Below `sm` the
row keeps the back arrow, the title and icon triggers.
The account menu is UserMenu.vue, extracted from Nav.vue, which keeps
its look; the header also hosts User and Preferences so sign-in,
benefits & usage and the preferences sheet work on every page. Each
Preferences instance opens only on the page on screen and closes when
its page is left; My Achievements off the homepage navigates home first.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
App.vue wraps the router view in a KeepAlive holding Home and the tool
page, so leaving for /tools/:slug and coming back neither re-runs the
homepage tests nor loses the scroll position; ToolPage (StandaloneTool
renamed) reads its slug reactively — the same instance now switches tool
on in-app navigation — and caches each visited tool per slug in its own
KeepAlive (max 8), dropping them all when the signed-in account goes
away (utils/tool-cache.js).
Cached pages pause while hidden: composables/use-route-active.js gives
useRouteActive / useActiveValue / useActiveEventListener, and the head
writer, the shortcut map, the scroll trackers, the ?q= watchers of ASN
Profile and IP Calculator, the User dialog triggers and the overlay
counter all act only for the page on screen. An overlay still open when
its page is left closes, as its portal lives outside the cached page.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
The layout refactor moved the two re-export bridges into utils/ip/ and
utils/features/; their header and test comments still named the old
paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new In-depth Checks tool shows which blocklists an IP is on: the
real-time DNSBLs and the threat intelligence lists the private API's
/iphitlist checks. Listings are split by kind, so a VPN or Tor exit
reads as an anonymity mark rather than a bad record; hits stay open with
their reasons and removal links, the remaining lists fold away, and a
legend explains every mark.
/api/ipblocklist proxies the upstream with the caller's headers and a
30s timeout (a cold DNSBL pass outlasts the default 8s), passing sign-in
and quota rejections through. Signed-in users only, original site only,
150 checks a month (ip_hitlist in the usage dialog and the pre-flight
quota gate). Shift+B opens it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fifteen cards in one flat grid mixed network diagnostics, lookups and the
in-depth checks that only run on IPCheck.ing. Each registry entry now
carries a category, and Advanced.vue renders the cards under three
headed groups: Network Diagnostics, Lookup & Calculation and In-depth
Checks. A group whose tools all fail their gates drops out with its
heading, so a self-hosted deployment simply shows no In-depth Checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
common/ had become the backend's library: 30 of its 41 modules were
imported by nothing in the frontend, so the name no longer said what
belonged there. It now holds only what the frontend or the build imports
too; backend-only code moved to server/, flat except for datasets/ (the
dataset engine and its readers). The *-db/ dataset directories stay in
common/: self-hosting docs and Docker volumes mount those paths.
frontend/utils/ keeps its app-wide modules at the root and gains ip/
(IP / ASN shaping and validation), report/ (the report pipeline) and
features/ (the pure layer of a single feature). Paths only — no logic
changes. The Dockerfile copies server/, and the AGENTS.md files say where
a new module goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Returning early on empty input left the previous result and error on
screen. It now clears them first, and the button stays disabled for a
whitespace-only input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skipping a row while this process published it also dropped a change
someone else made in that window: no file moved afterwards, so no later
check came. The watcher now looks again once the publish has recorded its
files, and reloads if they are no longer its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watcher told its own publish apart by the fingerprint reloadRow
records, but that lands only after the reload finishes. A settle tick in
between — the state write and a slow reload outlast the settle window, as
on a loaded CI runner — saw new files under an old fingerprint and reloaded
them a second time as a "file change". Rows being published by this
process are now skipped by the watcher until that reload has recorded them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared parser rejects an empty string, so Enter in the empty field
showed "Invalid physical address". onSubmit now returns early on empty
input, as IP Calculator does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isRand marked every locally administered unicast address, CID ranges
included. A CID is assigned local space (IEEE 802c ELI), so an address a
registered CID covers is no longer reported as random. For the rest the
U/L bit stays the signal: it is the only mark randomization leaves on an
address, and what the previous provider reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The success test read the checkout's downloaded registries and fell back
to asserting a 503 without them, so CI never exercised a 200. The handler
now comes from createMacChecker(lookup); the tests pass a fixture lookup
for the 200 and a null one for the 503.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three review findings in the IEEE registry reader:
- A country code after the postal code now wins over a state code before
it ("Dover DE 19901 US" → US), except after NL / IT, whose postcodes are
followed by letters (Dutch letter pairs, Italian provinces). Over the
live registries this corrects two addresses and changes no other.
- A registry file that stops mid-row, or inside a quoted field, is refused
as truncated; IEEE ends every row, the last included, with CRLF.
- A multicast query shows its block's multicast range (01:00:5E:…), which
holds the address asked about, instead of the unicast registration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The form validates with the shared common/mac-input.js parser, so vendor
prefixes go through. A collapsed "See examples" fold under the input, as in
IP Calculator, holds one pill per kind of answer: full address, vendor
prefix, MA-M block, private registration, multicast and randomized. Browsers
expose no MAC address, so there is no "my address" pill. Changelog entry in
every full locale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>