198 Commits
Author SHA1 Message Date
jason5ng32andClaude Opus 5.5 896494d895 Refactor(ip-infos): split IpDetailPanel into advanced and ASN blocks
IpDetailPanel had grown to ~750 lines holding three independent regions.
It is now the shell (metadata grid, inline Map / Country-traffic panels,
dialogs) with unchanged props, emits and multi-root template, rendering
IpAdvancedBlock (with ScoreDetailsPanel beside ScenarioFrictionPanel) and
IpAsnBlock, which relay view-usage / open-tool through it.

The inline /api/asn-history fetch becomes loadAsnHistoryInto in
utils/ip/asn-history.js, matching the other ASN loaders, now with tests.
Markup, copy, analytics and per-IP reset behaviour are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:19:36 +08:00
jason5ng32andClaude Opus 5.5 fd5e04fea6 Feat(ipcard): add abuse activity and scenario friction to the advanced data
The advanced block gains two signed-in fields under the existing four:
Abuse activity (the reputation level) and Scenario friction, ten usage
scenarios each rated smooth / extra checks / may be restricted. The card
shows one tone dot per scenario instead of a score, so it can't be read
as a second quality score; the details list follows the score details'
layout and closes with a Persona Check invitation.

Green and yellow only (a new light-yellow ok-mild tone for extra checks),
no reasons shown, copy in every full locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:12:28 +08:00
jason5ng32andClaude Fable 5.1 7a608cb8ce Feat(footer): fold the product links into a four-column footer
The Raycast / curl / Lite badges sat in an unlabelled image row of their
own under the Advanced Tools. The footer now has four columns aligned
with that card grid — brand, Project, Docs & Support, Clients — so the
product entries get a heading and show on every route; the curl
commands keep their dialog. Docs links share one locale-aware helper.
The footer never repeats on-page content, and the fixed footer height
in style.css goes, as the columns size themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 18:45:09 +08:00
jason5ng32andClaude Opus 5.5 88423522e3 Feat(report): add the Ad & Tracker Blocking Test to shareable reports
A blocker setup is part of the visitor's own environment, like the
browser info and proxy-rule sections, and is worth handing to whoever
helps debug it. The section stays deliberately small: request counts per
category (blocked / not blocked / no answer / total), no per-address or
per-vendor rows, so a report stays readable and the list can change
without breaking old links. Group ids are capped strings rather than an
enum for the same reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:04:09 +08:00
jason5ng32andClaude Opus 5.5 06402ce688 Feat(adblocktest): add the Ad & Tracker Blocking Test
People running uBlock Origin, Privacy Badger, a blocking browser, or a
DNS / VPN / router filter had no way here to see what their setup lets
through. The new tool probes a static list of widely used ad, analytics,
social, error-monitoring and device-maker endpoints plus popular tracking
scripts (fetched with no-cors and no cookies or referrer, never run), and
places bait elements for cosmetic filters. Nothing runs until the visitor
starts it; every group starts selected.

A timeout reads as "no answer" and stays out of the score, and every
listed host was checked to resolve, serve valid HTTPS and not redirect to
http:// or in a loop, since any of those would fail like a block. Element
hiding is left out of the score (DNS-level filters can never pass it) and
instead tells browser-level from network-level blocking. The page copy
says outright that blocking everything is not automatically better.

Ships with the x shortcut, locale copy for every full locale and a
changelog entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:53:43 +08:00
jason5ng32andClaude Opus 5.5 79a91171f7 Refactor(probes): take chain options as an object and share version validators
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>
2026-10-15 23:28:12 +08:00
14649814andAntigravity f4d90fe44c Fix(probes): strictly validate IP version on dedicated v4/v6 chains (#504)
- Require isValidIP && isIPv6 on IPv6-only probes (ipChecking6, cloudflareV6)
- Require isValidIP && !isIPv6 on IPv4-only probes (ipChecking4, cloudflareV4)
- Export isIPv4 and isOnlyIPv6 and add dedicated unit tests in getips.test.js

Co-authored-by: Antigravity <antigravity@google.com>
2026-10-15 23:25:19 +08:00
jason5ng32 47dff987b1 Improvements 2026-10-13 09:48:16 +08:00
jason5ng32 a509b99207 Improvements 2026-10-09 14:23:04 +08:00
Claude 65ec652135 Refactor(auth): one auth domain per deployment
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
2026-10-06 01:38:29 +00:00
Claude ebe2c36ae3 Fix(auth): offer GitHub sign-in in the installed PWA too
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
2026-10-05 17:04:44 +00:00
Claude 96525d2fc7 Chore: trim the branch — leftovers, redundancy, comments
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
2026-10-05 16:50:16 +00:00
Claude 47eca1a4f6 Chore(debug): remove the ?debug=nav overlay
The stall it was built to diagnose is fixed (utils/revive-animations.js);
the overlay, its App.vue gate and the event it listened to go.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
2026-10-05 16:39:22 +00:00
Claude a4020442f0 Fix(ui): revive the page's CSS animations after a history step
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
2026-10-05 16:27:19 +00:00
Claude 563c55c6d8 Style: slide between pages with View Transitions
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
2026-10-05 16:10:07 +00:00
Claude fd9b042019 Revert(ui): drop the page transitions
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
2026-10-05 16:05:37 +00:00
Claude d6d80dac24 Chore(ui): device-side switch for page transitions
`?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
2026-10-05 15:58:12 +00:00
Claude 38a61fdf92 Fix(ui): restart the Nav's animations after a page transition
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
2026-10-05 15:47:43 +00:00
Claude 1c7e7c68cd Fix(auth): give the installed PWA its own auth domain
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
2026-10-05 15:28:57 +00:00
Claude 856f965101 Style: slide between pages with View Transitions
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
2026-10-05 15:04:12 +00:00
Claude e3eaef369f Fix(ui): one scroll offset for section shortcuts and nav links
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
2026-10-05 14:51:26 +00:00
Claude 08473e3e54 Fix(auth): sign in by redirect inside the installed PWA
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
2026-10-05 14:50:45 +00:00
Claude f0d27fe510 Refactor(nav): one navigation bar on every route, breadcrumb in the page
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
2026-10-05 14:43:20 +00:00
Claude 2f5d3b426b Feat(tools): open the Advanced Tools as pages
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
2026-10-05 14:25:02 +00:00
Claude 728dd7d087 Refactor(nav): shared page header with tools and account menus
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
2026-10-05 14:10:34 +00:00
Claude 44e897a48b Refactor(router): keep the homepage and tool pages alive
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
2026-10-05 13:56:19 +00:00
jason5ng32andClaude Opus 5.5 deb7f11468 Feat(tools): add the IP Blocklist Check
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>
2026-10-05 14:15:21 +08:00
jason5ng32andClaude Opus 5.5 c29e48687b Refactor(layout): split backend-only code out of common/, group frontend utils
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>
2026-10-05 13:10:02 +08:00
jason5ng32andClaude Opus 5.5 7a52e8acea Feat(mac): answer MAC Lookup from the local IEEE registries
MAC Lookup no longer calls maclookup.app. The five public IEEE registries
(MA-L, MA-M, MA-S, IAB, CID) are a new `oui` dataset row — no key needed,
published as downloaded, refused below per-registry row floors — and
common/oui-db.js answers the longest assignment covering a query, the group
bit masked so a multicast address resolves to the block it derives from.
The country is read off the end of the registered address.

?mac= now takes a vendor prefix as well as a full address (6–12 hex digits,
colon / hyphen / dotted / bare), parsed by common/mac-input.js, which the
form shares. An address no block covers is still answered, with its flags.
The route is gated by requireOfflineData while the registries download at
boot. MAC_LOOKUP_API_KEY is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:21:12 +08:00
jason5ng32andClaude Opus 5.5 b3cd1ceef7 Feat(backend): listen before offline data finishes downloading
The backend downloaded every missing dataset before listening, so a first
boot (or a newly added dataset) refused every request for a minute or more.
Snapshots already on disk now load before the listener opens and missing
ones download behind it. Meanwhile only routes reading still-missing data
answer 503 (never edge-cached): /api/maxmind, /api/asn-connectivity and
/api/service-status. An ASN Profile section whose data is still loading
answers 'error' on its own, and the connectivity graph client doesn't cache
a 503, so both recover once the data lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:22:06 +08:00
jason5ng32andClaude Opus 5.5 57f68bacb5 Fix(asn): keep incomplete country shares and stale profiles out of the cache
buildCountryShares now reports whether its walk completed: a lookup with
no answer (no database, or a failed lookup) and the lookup budget, now a
hard cap across blocks, both mark it incomplete, and the bgp-prefixes view
serves such an answer uncached. A prefix-less AS needs no lookups, so it
stays cacheable even without MaxMind. ASN_PROFILE_VERSION goes to 6 so
profiles cached with first-address country shares are not served on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:57:54 +08:00
jason5ng32andClaude Fable 5.1 d5715104d5 Fix(asn): never cache an upstream fault as an empty answer
Review findings on the ASN Profile work, mostly one class of bug: a failed
upstream read as "no data", which the 7-day edge cache would then pin.

- AS Rank: a 200 carrying GraphQL errors is a failure, not an unknown AS.
- Radar: an answer built from partly failed segments is still shown but
  flagged `incomplete` and kept out of the cache, on the aggregate and on
  the standalone asn view. A 404 is Radar's "no such AS" and counts as
  empty, as does a segment that simply has no figures.
- Reputation: a malformed or non-200 answer is an error.
- Prefixes: a payload without `prefix_origins` is an error; [] stays valid.
- Radar is `empty` when only local CAIDA counts are present.
- requireValidASN enforces AS1–AS4294967295 and canonicalizes; the range
  rule lives once in common/asn-input.js, shared with the frontend.
- The PeeringDB listing lookup goes through fetchUpstream.
- New functions follow the const-arrow convention; test stubs compare
  parsed hostnames instead of URL substrings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:43:55 +08:00
jason5ng32andClaude Fable 5.1 4eee98aa93 Feat(asn): PeeringDB data in ASN Profile
A third offline dataset joins the CAIDA updater: the daily PeeringDB dump,
distilled by a streaming pass into a compact per-ASN index (network type,
scope, peering policy, exchange and facility presence). Contact tables are
never parsed and the raw dump is deleted after distilling. It downloads
only when the Cloudflare key is configured, since the tool is hidden
without it.

The profile shows network type, scope and website in the header and a
"Peering & facilities" card whenever the AS lists an exchange or facility.

Aggregate deadlines now sit above the fetch timeouts they wrap, so a
source that degrades internally still delivers its partial answer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:31:11 +08:00
jason5ng32andClaude Fable 5.1 5ea781c513 Chore(asn): changelog entry and cleanup for ASN Profile
Adds the v7.7.0 changelog line for the new tool, drops two exports
nothing imports, and aligns the reputation test fixtures with the fields
the private API actually returns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:43:29 +08:00
jason5ng32andClaude Fable 5.1 901092ef66 Feat(asn): ASN Profile tool
A full profile of one autonomous system at /tools/asn?q=AS<n>, served by
a single /api/asn-profile request: identity and registration with the raw
Whois record, key facts (rank, announced IPv4, prefixes, users), traffic
shares and connection quality, where its IPv4 space is used, the announced
prefix list with RPKI status, and connectivity with the topology graph.
A reputation card appears beside the header when the backend returns it.

- The ASN Info panel now opens the profile instead of linking out.
- ASNConnectivity gains `expandable` / `bordered` so the profile can embed
  it without the nested drawer and the second border.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:33:51 +08:00
jason5ng32andClaude Fable 5.1 c55634c8e0 Feat(asn): frontend groundwork for the ASN Profile tool
- Registry gate `requiresConfig`: a tool is listed in the Advanced Tools
  grid and the nav only when its backend config is present; the listing
  filter is one pure function. ASN Profile requires the Cloudflare key.
- `useAsnInfo` owns the Radar ASN fetch and its session cache, shared by
  the IP cards and the query dialog.
- ASN Info's traffic-share and connection-quality blocks become small
  components over pure helpers, reusable by the profile page.
- ASN Profile lands as a mock-data page for now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:47:15 +08:00
jason5ng32andClaude Fable 5.1 09d3cef4c0 Feat(whois): look up AS numbers
The Whois tool and /api/whois now take `AS<n>` (prefix in any case) next
to IPs and domains, answered from the RIR's RDAP autnum record. A bare
number stays invalid input, so it can never be mistaken for anything else.

The raw blocks no longer start with the template's own indentation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:10:14 +08:00
jason5ng32 3dea2421d2 Improvements 2026-09-29 16:00:44 +08:00
jason5ng32andClaude Opus 5.5 cf3bb69d8b Feat(ipinfo): show how the quality score was reached and rename the proxy field
Follows the v3 score output of MyIP-api (this frontend no longer reads
the v2 shape; deploy it right after the API).

- A toggle next to the quality score opens a panel: a short explanation
  assembled from the score dimensions (network type, anonymity with its
  last-observed date, reputation, the ASN's neighbourhood, the verdict),
  and all boolean attributes as check / cross / question-mark icons.
  Residential proxies seen only in the API's own sighting history read
  as "suspected".
- The card's "Proxy" field becomes "Anonymizer": Tor exit, iCloud
  Private Relay, residential proxy, proxy, VPN (each with a suspected
  variant when only one source agrees) or not detected. Card fields are
  anonymity / anonymityCode / anonymityProvider / anonymityProtocol; the
  provider line hides when the API returns null. Texts live under
  ipInfos.anonymity.*, with one code-to-key map for card and report.
- Diagnostic reports store the anonymity code (REPORT_VERSION 2; v1
  links show the unsupported-version page, reworded to fit either
  direction). The persona collector no longer sends the unused isProxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:19:14 +08:00
jason5ng32andClaude Opus 5.5 78d5f95238 Chore(history): cap IP history retention at 30 days
Ninety days is longer than most visitors need for a local record. Stored
preferences above the new cap clamp to 30 on read, and older day buckets
are pruned on the next load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 09:54:53 +08:00
jason5ng32 5c59c6677d Improvements 2026-09-24 09:37:12 +08:00
jason5ng32 7f3d38e147 Add Map to IP History 2026-09-21 23:11:43 +08:00
jason5ng32andGPT-6 5d764cff29 Feat(history): group IP records with a daily view
Collapse repeated daily IP records into summaries ordered by last seen.
Keep country and IP-type filters across both views and expand dates only
for IPs recorded on multiple days. Preserve the daily storage format.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-21 22:43:01 +08:00
jason5ng32 40342a9631 Improvements 2026-09-18 10:00:47 +08:00
jason5ng32andGPT-6 1ec87945ce Fix(ipcalculator): parse octal input directly as BigInt
Avoid Number overflow when parsing long standalone or dotted octal
input. Cover oversized values, leading zeros, and IPv4 boundaries.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-17 10:49:17 +08:00
jason5ng32 45be0e711c Improvements 2026-09-17 10:33:38 +08:00
jason5ng32andGPT-6 b2b98b519c Fix(dns): share fallback probes within each test run
Reuse pending, successful, and exhausted provider probes across cards.
Keep refreshes isolated and collapse repeated observations in reports.
Cover shared retry budgets, refresh races, and report deduplication.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-17 10:23:24 +08:00
jason5ng32andGPT-6 a11539004c Fix(dnsleaks): obtain bash.ws test IDs from the provider
Keep a single probe and use bash.ws as a fallback after Surfshark.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-16 18:49:22 +08:00
jason5ng32 020bfcedd6 InfoBanner support open sheet 2026-09-08 09:26:13 +08:00
jason5ng32andGPT-6 6cf3b58a6b Fix(speedtest): improve feedback on slow connections
Show a fixed preliminary test before the selected measurements and keep
formal results independent. Start testing without waiting for charts or
connection metadata, buffering samples until charts are ready.

Add localized phase labels and immediate startup progress, reduce the
formal latency sample count to 20, and document the improvement.

Validation: pnpm check (1533 tests passed and build succeeded).

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-05 09:37:15 +08:00