100 Commits
Author SHA1 Message Date
jason5ng32 da04a81e58 Improvements 2026-10-29 09:46:34 +08:00
jason5ng32 e461bc168b Improvements 2026-10-29 09:46:17 +08:00
jason5ng32 2d244a129d Improvements 2026-10-27 12:41:09 +08:00
jason5ng32 1b1cf9f68f Improvements 2026-10-27 11:47:08 +08:00
jason5ng32 31e8a5c306 Improvements 2026-10-26 00:08:51 +08:00
jason5ng32 ccd7a572a8 Improvements 2026-10-25 23:32:37 +08:00
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 825db1638f Chore: bump version to 8.1.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:12:28 +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 Opus 5.5 e4ec314a6a Fix(connectivity): trim the Mexico and India import lists
Drop SAT (times out from outside Mexico) and Rapido (CORP same-origin
blocks the probe), plus the India members that are corporate or B2B
sites rather than ones people open day to day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:31:35 +08:00
jason5ng32 0c3f05ca45 Merge branch 'dev' of https://github.com/jason5ng32/MyIP into dev 2026-10-24 21:29:38 +08:00
jason5ng32 2329294a8f Improvements 2026-10-24 09:29:34 +08:00
jason5ng32andClaude Fable 5.1 4acf56b593 Feat(footer): add a Contribute entry to the Project column
Links to the repository's GitHub /contribute page, which lists the open
good-first-issues above the contributing guide.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 22:03:39 +08:00
jason5ng32andClaude Fable 5.1 c0ae017f89 Feat(connectivity): invite visitors to add their country's import list
Closes the "Import a List" grid with a muted line linking to the open
country-list issue, so a visitor who finds their country missing sees
where to add it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-23 22:03:39 +08:00
jason5ng32andClaude Opus 5.5 f6e979f52d Fix(ci): generate Docker image labels with metadata-action
Multi-line release notes in the labels input split into one label per
line and an empty line aborted buildx. Labels now come from
docker/metadata-action with the version pinned to RELEASE_VERSION, and
version inputs reach the shell through env instead of interpolation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:41:04 +08:00
jason5ng32 720c4a1a64 Improvements 2026-10-23 21:09:41 +08:00
jason5ng32 13af650719 Merge branch 'dev' of https://github.com/jason5ng32/MyIP into dev 2026-10-23 21:01:19 +08:00
jason5ng32andClaude Opus 5.5 a98a6c812f Feat(service-status): add Perplexity, Canva and Docker
Reposition the tool from AI services to well-known internet products:
the Note and the tool card copy change in every full locale, with a
v8.0.0 changelog entry. Perplexity and Docker read the incident.io
structure; Canva is a plain Statuspage row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:59:34 +08:00
jason5ng32andClaude Opus 5.5 a79e88cbb1 Feat(service-status): add per-provider component rename
A `components.rename: { <id>: '<label>' }` selector relabels upstream rows
by component id after selection, so include / exclude keep matching the
upstream names. npm's "www.npmjs.com website" row now reads "Official
Website".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:51:51 +08:00
jason5ng32andClaude Opus 5.5 e766bd84ce Fix(api): make guard query rewrites survive Express 5
Express 5 exposes req.query through a getter that re-parses the URL on
every read, so the guards' in-place normalization was silently dropped:
?type=caa reached dns-resolver lowercase, ?asn=AS13335 reached the ASN
handlers unparsed, ?q=as13335 missed whois's AS<n> branch. Guards now pin
the parsed query as an own property before rewriting it, and a regression
block exercises them on a real Express request.

Fixes #521

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:24:56 +08:00
jason5ng32 7fbc782f1d Improvements 2026-10-23 11:24:02 +08:00
jason5ng32andClaude Fable 5.1 9e4a0f5cb6 Improvements
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 11:39:44 +08:00
jason5ng32andClaude Fable 5.1 da1f3fe952 Improvements
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 11:37:55 +08:00
jason5ng32andClaude Fable 5.1 4c9ab04bc5 Improvements
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 11:37:55 +08:00
jason5ng32 11b4160767 Improvements 2026-10-22 11:06:30 +08:00
jason5ng32 9ee262fdc3 Improvements 2026-10-22 10:25:46 +08:00
jason5ng32andClaude Fable 5.1 c350f0d2d9 Feat(ruletest): test twelve proxy rules, not eight
Four more ptest workers are live, so the Rule Test grid grows to
twelve cards and the README rule snippets cover them. The note above
the grid used to spell out the count in every locale; it now takes
the number from the card list, so the next change in count touches
one line. The CrossingTheWall rule keeps its eight-IP threshold.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 00:10:16 +08:00
jason5ng32andClaude Fable 5.1 938dca96dd Fix(service-status): pick sub-services that say something
Supabase's card showed one region and Cloudflare's read "major
outage" on every visit. Supabase and five other providers are
incident.io pages, whose Statuspage-compatible summary flattens
groups away and, for Supabase, returns one component of 189; those
providers now also read the native /proxy/<host> structure and list
its top-level groups, each rolled up to its worst visible child.
Cloudflare's top level is seven continents that go red whenever one
PoP is re-routed, so its card lists a fixed set of products instead
and takes its light from them, not from the page indicator.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-21 23:59:50 +08:00
jason5ng32 d5452c0fce Improvements 2026-10-21 14:31:47 +08:00
jason5ng32andClaude Fable 5.1 e7bc4db293 Feat(analytics): track how the Security Checklist is read and used
GA saw only category switches and resets, so a month of visits could
not say whether anyone read an item's details or ticked one. Four
labels on the existing SecurityChecklist event — ItemInfo and
CategoryIntro when a details panel or intro opens, Check and Ignore
when an item is ticked or set aside — read as a funnel after
ChangeList. Only the positive direction fires; closing and undoing
stay silent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-21 12:24:21 +08:00
jason5ng32andClaude Fable 5.1 49c3dfa887 Feat(tools): move the Security Checklist into a reference group
The checklist is content to read and work through, not a test that
runs, yet it sat among the network diagnostics where a card promises a
result. A fourth group, 'reference', now closes the Advanced Tools grid
and holds it alone; the group's title lands in every full locale, and
the self-hosted grouping test expects the group to survive the gates.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-21 12:24:21 +08:00
jason5ng32 5cfb73994a Improvements 2026-10-21 11:33:54 +08:00
jason5ng32andClaude Opus 5.5 45f920248f Fix(analytics): one NavClick label per tool, sent once
Tool opens labelled NavClick with the slug's first letter capitalized
('Pingtest'), while keyboard shortcuts sent a second, hand-written label
('PingTest', 'AsnProfile') on top of the one openTool already sent. GA
labels are case-sensitive, so one tool split into two rows and every
shortcut open counted twice. The label is now the slug as-is, the same
id as /tools/<slug>, and shortcuts rely on openTool's event alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:44:56 +08:00
jason5ng32andClaude Opus 5.5 50885c6317 Fix(pulse): show cloud-origin outages and drop empty Radar events
Radar files some events against a cloud region (originDetails) rather
than a location or ASN. The feed ignored that subject, so an ongoing
ORIGIN anomaly rendered as a blank first row and escaped dedupe against
its matching outage. Origins are now mapped, shown, and matched in
dedupe; stub annotations with no subject and no text are dropped. The
new PLATFORM outage level gets a translation in every full locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:42:45 +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 Fable 5.1 d28fe239ae Fix(nav): collapse the link row by measured width, not by isMobile
Tablets report a desktop UA, so between 768px and ~1366px the flat
link row rendered and wrapped inside its links; no single breakpoint
fits six locales. use-nav-overflow.js measures the row against the
space beside the brand and actions and collapses to the hamburger
when it doesn't fit. The row stays rendered (invisible, inert) so it
remains measurable. DocsSearch switches to its icon below xl by CSS so
the action group's width never depends on the collapse state.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 17:54:46 +08:00
jason5ng32 35e691da6e Improvements 2026-10-19 23:45:41 +08:00
jason5ng32 072a76cae0 Improvements 2026-10-19 23:18:32 +08:00
jason5ng32andClaude Opus 5.5 4cc48d151b Fix(adblocktest): state what the probes cannot tell
A probe is a plain fetch from this page, so a filter rule aimed only at
one request type (e.g. `$script`) can let it through while still
blocking the real script on a website; a line under the list version now
says so. The empty "not blocked only" / "blocked only" views claimed
everything was (or nothing was) blocked even when results had timed out
or were still pending; they now say only that no request was confirmed
in that state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:15:51 +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 1163ea4a13 Docs(readme): list the Ad & Tracker Blocking Test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:53:43 +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 c2751d37f7 Feat(ui): add the shadcn-vue Alert component
Notices across the tools are hand-rolled boxes; Alert gives new ones the
primitive instead. In this JavaScript setup the CLI imports `cn` from a
bare "cn" module and installs an unrelated npm package of that name, so
the dependency is dropped and the components import from @/lib/utils;
frontend/AGENTS.md records the trap for the next `shadcn-vue add`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:53:29 +08:00
jason5ng32andClaude Opus 5.5 b84a1d3764 Refactor(ip-infos): drop the default map images
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>
2026-10-17 09:35:31 +08:00
jason5ng32andClaude Opus 5.5 6e40a611d3 Fix(api): require a tag-shaped language in the map proxy
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>
2026-10-17 09:35:27 +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
jason5ng32 70c1cbd2b9 Merge branch 'sponsors-2026-10b' into dev 2026-10-14 18:02:07 +08:00
jason5ng32andClaude Opus 5.5 547e9a1e84 Docs(sponsors): add Proxy-Seller and link sponsor names
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>
2026-10-14 18:01:52 +08:00
jason5ng32 5d378c5e13 Improvements 2026-10-13 09:54:52 +08:00
jason5ng32 47dff987b1 Improvements 2026-10-13 09:48:16 +08:00
jason5ng32 6036a7c570 Merge branch 'readme-supporters-layout' into dev 2026-10-11 10:41:18 +08:00
jason5ng32andClaude Opus 5.5 068ea5a795 Docs(sponsors): separate supporters and stack their logos
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:40:29 +08:00
jason5ng32 e23f00a818 Merge branch 'sponsors-2026-10' into dev 2026-10-10 10:24:47 +08:00
jason5ng32andClaude Opus 5.5 2961d9ba8d Docs(sponsors): add SwiftProxy and split paid sponsors from supporters
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>
2026-10-10 10:24:19 +08:00
jason5ng32 a509b99207 Improvements 2026-10-09 14:23:04 +08:00
jason5ng32 187ede91a9 Improvements 2026-10-07 15:34:08 +08:00
jason5ng32andClaude Opus 5.5 017e5c9af3 Feat(ip-infos): point score details to a live blocklist check
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>
2026-10-07 15:26:02 +08:00
jason5ng32 117ebaa65a Improvements 2026-10-07 10:03:06 +08:00
jason5ng32andClaude Opus 5.5 89a103a3b7 Fix(footer): open the About sheet only on the page on screen
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>
2026-10-07 00:45:35 +08:00
jason5ng32 6d7221538d Improvements 2026-10-06 13:03:58 +08:00
jason5ng32 8d7b53d1de Improvements 2026-10-06 11:35:36 +08:00
jason5ng32 b616b7e00e Improvements 2026-10-06 10:48:50 +08:00
jason5ng32 8543879bbd Improvements 2026-10-06 10:33:31 +08:00
jason5ng32 78db2f8934 Improvements 2026-10-06 10:16:04 +08:00
jason5ng32 ae52320d3b Improvements 2026-10-06 00:57:30 +08:00
jason5ng32 d8426e6017 Improvements 2026-10-05 15:06:00 +08:00
jason5ng32 bbcc53c518 Improvements 2026-10-05 14:59:24 +08:00
jason5ng32 4840c9ce01 Merge branch 'dev' of https://github.com/jason5ng32/MyIP into dev 2026-10-05 14:52:09 +08:00
jason5ng32 b1875562fc Improvements 2026-10-05 14:43:48 +08:00
jason5ng32andClaude Opus 5.5 1dd10fbd2f Chore: point the asn-input / mac-input comments at the moved bridges
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>
2026-10-05 14:41:22 +08:00
jason5ng32 fa7ec2108e Improvements 2026-10-05 14:17:38 +08: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 8c02ab3d14 Feat(tools): group the Advanced Tools cards by category
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>
2026-10-05 13:24:32 +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
jason5ng32 ec4d536b60 Improvements 2026-10-05 12:20:03 +08:00
jason5ng32 d0e4023ce4 Cache Improvements 2026-10-05 12:17:18 +08:00
jason5ng32 c0cd8418e3 Improvements 2026-10-05 11:58:32 +08:00
jason5ng32 95f4196c88 Improvements 2026-10-05 11:56:44 +08:00
jason5ng32andClaude Opus 5.5 a1bcc62965 Fix(ui): an empty MAC Lookup submit clears the last answer
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>
2026-10-05 11:38:08 +08:00
jason5ng32andClaude Opus 5.5 0b4069b1b2 Fix(datasets): defer, not drop, a watcher check during this process's publish
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>
2026-10-05 11:38:08 +08:00
jason5ng32andClaude Opus 5.5 3072941edc Fix(datasets): never take this process's own publish for a foreign change
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>
2026-10-05 11:32:37 +08:00
jason5ng32andClaude Opus 5.5 d3df177df9 Fix(ui): Enter on an empty MAC Lookup input does nothing
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>
2026-10-05 11:29:50 +08:00
jason5ng32andClaude Opus 5.5 87d49cec3d Fix(mac): a Company ID's range is not a randomized address
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>
2026-10-05 11:29:09 +08:00
jason5ng32andClaude Opus 5.5 000d5b4181 Fix(tests): run the MAC Lookup handler over fixture registries
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>
2026-10-05 11:28:13 +08:00
jason5ng32andClaude Opus 5.5 675ad8967b Fix(mac): trailing country code, cut-short files, multicast range
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>
2026-10-05 11:28:13 +08:00
jason5ng32andClaude Opus 5.5 e57b9e331d Feat(mac): prefix queries and example pills in MAC Lookup
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>
2026-10-05 11:21:12 +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 6a563e437f Fix(datasets): log every scheduled run and each row's outcome
A scheduled or catch-up run that found every dataset current logged
nothing — only a publish wrote a line — so a run that fired looked like one
that never did. updateDatasets now logs the run's start and, per row, that
it was up to date or skipped for another process's lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
2026-10-05 02:23:24 +00:00
jason5ng32andClaude Opus 5.5 c29de4f97c Fix(tests): give the healthy row headroom in the stalled-row timeout test
updateDatasets applies one timeout to every row, so the 50 ms that stops
the stalled row also bounded the healthy one — a lock, a temp dir and two
fsync'd state writes — and a loaded CI runner timed it out too. 1 s keeps
the stall test meaningful with room for the healthy row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
2026-10-05 01:52:28 +00:00
jason5ng32andClaude Opus 5.5 971cc5203c Fix(datasets): repair an interrupted publish or an unusable snapshot at boot
- A run that died or lost its lock between renames left every file present
  (possibly a mixed MaxMind City / ASN pair), and the boot download skipped
  the row because the files existed. The in-flux state now carries
  `publishing: true`, cleared by the final record, and boot re-fetches a row
  whose last publish never finished.
- The boot skip asked whether a snapshot file existed, so an empty or
  corrupt one — which the reader refuses — kept the row unrepaired. Rows now
  supply isLoaded(), the reader's own readiness (all four), and boot
  downloads whenever the reader holds nothing usable; files under the
  published names are re-fetched even when the state records the remote's
  version (updateDataset's new `force`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
2026-10-05 01:45:46 +00:00
jason5ng32andClaude Opus 5.5 f81fce0c02 Fix(datasets): roll back a publish a timeout cuts short; skip only on a lost lock
publish took the combined signal (caller timeout + lock loss) and treated
any abort as a lost lock, so a scheduled or boot timeout firing between
renames skipped the rollback and cleanup while this process still held the
lock, leaving a mixed set and .next / .bak files. The combined signal still
cancels, but only the lock's own signal now stops rollback and cleanup.
Once every rename is done, only a lost lock keeps the version from being
recorded, so a timeout landing after a complete publish no longer forces a
re-download.

The lock-loss test now trips the engine's real onCompromised handler; a
new test covers the timeout path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
2026-10-05 01:45:45 +00:00
jason5ng32andClaude Opus 5.5 f867e0d189 Fix(datasets): watch from before the boot downloads; honour hand-named CAIDA files
- The watcher started only after every boot step, so a dataset published
  by another process (or placed by hand) while another step was running
  became the watcher's baseline and was never loaded. It now starts before
  the boot downloads.
- The CAIDA readers serve the newest *.txt under any name, but the boot
  download checked only the published names, so an upgrade with a
  hand-named snapshot re-downloaded it. A row can now supply hasSnapshot();
  as2org / as-rel use their reader's own rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:46:22 +08:00
jason5ng32andClaude Opus 5.5 1db89e1dc4 Fix(datasets): size the Sentry monitor's maxRuntime to the scheduled rows
Rows update one after another, each with up to 30 minutes, so four datasets
can legitimately take two hours while the monitor failed a run after one.
maxRuntime is now every scheduled row's timeout plus a 10-minute margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:44:05 +08:00
jason5ng32andClaude Opus 5.5 ce21aa33bb Fix(datasets): mark state in flux while publishing; no file work after a lost lock
Rolling back after a lost lock could overwrite what the lock's new holder
had just published. Once the lock is lost, publish now touches nothing more
(no rollback, no cleanup). To keep a half-done set from passing for
current, the state names no version from the start of publishing until the
final write, so whoever runs next — after a lost lock, a crash or a kill —
re-fetches and publishes a whole set. A rename failure with the lock still
held is rolled back as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:43:23 +08:00
jason5ng32andClaude Opus 5.5 c85c33d13a Fix(tests): expect a 502 from the ASN Profile handler test without CAIDA data
With connectivity now an error when no as-rel snapshot is loaded, the
network-down handler test has no working source on a data-less checkout
(CI), so the handler rightly answers 502. The test asserts that there and
keeps its 200 expectations where a snapshot exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:38:14 +08:00
jason5ng32andClaude Opus 5.5 034f5177c6 Fix(datasets): check for a lost lock between publish steps
The lock-loss signal was checked only before publishing began, so a lock
compromised mid-copy or mid-rename let publication continue. It is now
checked before every copy and rename, an abort taking the same rollback,
and again before the state is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:36:40 +08:00
jason5ng32andClaude Opus 5.5 5c0891a323 Docs(readme): use DATASET_AUTO_UPDATE in the docker run example
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:35:23 +08:00
jason5ng32andClaude Opus 5.5 d84fefbbea Fix(asn): report unloaded local data as an error, not a cacheable empty
- PeeringDB is 'disabled' only without a Cloudflare key; configured but not
  loaded (still downloading, or the download failed) is an 'error', so the
  ASN Profile isn't cached for a week without it.
- Connectivity without an as-rel snapshot is an 'error' too, not an empty
  graph; a missing as2org still only blocks while it downloads, as RIPEstat
  names the nodes.
- A CAIDA as2org / as-rel file that parses to nothing (corrupt) no longer
  counts as loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:35:23 +08:00
jason5ng32andClaude Opus 5.5 02476aec7e Fix(datasets): abort on a lost lock, roll back new files, validate watched files
- A compromised lock (failed heartbeat; another process may take it over)
  aborts the update holding it, download included, before it publishes,
  instead of only being logged.
- A failed publish removes files it had just added when the dataset had none
  before, as well as restoring replaced ones from backup: never half a set.
- The watcher reloads hand-placed or externally published files only once
  they have held still for the settle interval and passed the row's
  validator, so a file still being copied never replaces healthy data.
- Back to write-file-atomic 8, with engines aligned to its range
  (^24.15.0 || >=26.0.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:31:28 +08:00
jason5ng32andClaude Opus 5.5 65e8432768 Fix(datasets): bound scheduled runs, catch up by schedule, retry failed reloads
- Scheduled and catch-up runs give each dataset 30 minutes, download
  included, so a stalled upstream can't hold up the rest or, under croner's
  overrun protection, every later run.
- Downloads go through fetchUpstream (project User-Agent, a timeout on the
  wait for a response); the body stays bounded by the caller's signal.
- Catch-up asks whether a scheduled run fell between the last check and now,
  instead of a 25-hour age that missed a check skipped just after one.
- A remote that names no version (no Last-Modified) is always fetched, not
  taken for unchanged; maxmindIdentifier is null when an edition omits it.
- The watcher records a dataset's files as loaded only after the reload
  succeeds, so a failed load is retried on their next change.
- write-file-atomic 7, which supports the project's Node >=24 (8 needs 24.15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:29:48 +08:00