Skip to content

feat(core): add the inspector engine (phase 13) - #15

Merged
hami9 merged 3 commits into
mainfrom
feat/phase-13-inspector-engine
Aug 24, 2026
Merged

feat(core): add the inspector engine (phase 13)#15
hami9 merged 3 commits into
mainfrom
feat/phase-13-inspector-engine

Conversation

@hami9

@hami9 hami9 commented Aug 24, 2026

Copy link
Copy Markdown
Owner

What and why

Phase 13 of docs/roadmap.md. core can now answer three questions about a document it was handed — what built this site, what does it set in type, what images does it reference — so that the extension (phase 14) and the app (phase 15+) have something real to display and neither has to implement any of it.

Technology detection widens past storefronts. The seven platforms platform/detect.ts knows become ten categories: framework, CMS, analytics, tag manager, CDN, payment, chat, font service, error tracking, and storefronts alongside them. The scoring model is unchanged because it was already right — weighted markers, summed and clamped, with every marker that fired kept so §11's report can answer "why did it say that?".

detect.ts now runs on the shared engine in inspect/signals.ts rather than its own copy of it, so the two cannot drift. PlatformId and Layer A's dispatch are untouched: a ruleset that grows every month cannot also be a type something switches on, so the inspector returns an open string id and Layer A keeps its closed union.

The rules are written here, and the file says so at length. Wappalyzer's ruleset is the tempting shortcut and stopped being open source in 2023; several of the "open" forks carry its data unchanged and inherit the problem. §16 asks for the licence and provenance of every rule to be recorded in the file that holds them, so technologies.ts opens with that and names a source per rule set. An unattributed rule is now a defect rather than a loose end. The Persian widgets — Yektanet, Goftino, Raychat, ZarinPal, ArvanCloud — are in for the usual reason: they are what this project's users actually meet, and no foreign ruleset covers them.

Fonts and images are new. Fonts read @font-face, stylesheet links, the family and weight a font service was asked for in its query string, and inline styles. What needs getComputedStyle is an optional input the extension fills rather than something core tries to compute, since the same code runs in a service worker, in Node and in Tauri's WebView. That distinction is load-bearing in the output too: usedBy is only ever filled from measurement, so its absence means "not measured" and never "unused".

Images cover <img>, srcset, <picture><source>, CSS backgrounds, preloads, icons and social previews, resolved against <base href> where the document sets one, and merged by absolute URL. A hero image is routinely preloaded, given as src and listed in srcset four times, and a list that repeated it five times would be complete and useless. Icons and Open Graph previews keep their own origin kind so a future "download all" does not offer a 32px favicon alongside the product photographs.

Nothing here fetches. That is what lets the whole inspector run in a popup without a request against the shop's server and without touching §10's politeness accounting. Where a value can only be known by downloading the asset — byte size, natural dimensions — the field is absent rather than guessed.

Reviewer notes

parseSrcset is deliberately not a comma split. A resizing CDN writes /c_fill,w_300,h_300/pic.jpg, and splitting on commas turns one product image into two broken URLs. HTML's own parse takes the URL as a run of non-whitespace and only then looks for a descriptor, so that is what this does. There is a test with a comma-bearing CDN URL to keep it that way.

Empty results are answers, not failures, and are tested as such. A hand-written page genuinely uses no recognised technology. A confident wrong answer is the failure mode that matters most in a tool people read as a fact about a shop, so a rule that half-fires returns nothing rather than a low-confidence entry a UI would render as a fact.

Two commits are not phase 13. style(docs) fixes a Prettier failure in docs/store-listing.md that predates this branch — npm run check was not green on main, and the next phase would have inherited it. docs updates the README, which is worth a look on its own: it now says plainly that the inspector is an engine with no UI yet, because a public README implying a working feature here would be advertising something nobody can use. The stale test badge (760) is corrected there too.

How it was verified

npm run check passes in full — format, lint, typecheck, tests.

800 tests pass: the 762 that passed before this branch, plus 38 new ones in packages/core/test/inspect/inspect.test.ts covering every box in docs/prompts/phase-13.md.

Fixture-driven and offline, per §12 — no shop was contacted, and by design none can be: every function takes a document it was given. The new fixture packages/core/test/fixtures/inspector-shop.html is a WooCommerce shop behind Next.js with the analytics, chat, payment and CDN widgets a Persian storefront actually carries, plus the awkward image cases (one hero referenced five ways, a comma-bearing CDN URL, a lazy-loading placeholder). no-markup.html is reused as the honest-nothing case.

Not verified against a live shop, and cannot be at this phase: there is no surface that exposes any of this yet. Phase 14 is where the engine first meets real pages, which is the stated reason the roadmap puts it on the extension before the app.

Checklist

  • The commits follow Conventional Commits — they decide the next version number
  • npm run check passes (format, lint, typecheck, tests)
  • New behaviour has tests
  • No currency unit is inferred, and no field is invented to fill a blank

🤖 Generated with Claude Code

hami9 and others added 3 commits August 24, 2026 19:44
Prettier has been failing on this file since the table was added, which
means `npm run check` has not been green on `main` and the next phase to
run it would have inherited the failure as its own.

Whitespace only — the column widths change, the content does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 13. `core` can now answer three questions about a document it was
handed — what built this site, what does it set in type, what images does
it reference — so that the extension (phase 14) and the app (phase 15+)
have something real to display and neither has to implement any of it.

Technology detection widens past the seven storefronts `platform/detect.ts`
knows, to frameworks, CMSes, analytics, tag managers, CDNs, payment and
chat widgets, font services and error tracking. The model is unchanged
because it was already right: weighted markers, summed and clamped, with
every marker that fired kept so §11's report can answer "why did it say
that?". `detect.ts` now runs on the shared engine rather than its own copy
of it, so the two cannot drift; `PlatformId` and Layer A's dispatch are
untouched, because a ruleset that grows every month cannot also be a type
something switches on.

The rules are written here, from vendor documentation and vendors' own
embed snippets, and the file says so at length. Wappalyzer's ruleset is
the tempting shortcut and is no longer open source; several of the "open"
forks carry its data unchanged and inherit the problem. §16 asks for the
licence and provenance of every rule to be recorded in the file that holds
them, and an unattributed rule is now a defect rather than a loose end.
The Persian widgets — Yektanet, Goftino, Raychat, ZarinPal, ArvanCloud —
are in for the same reason they always are: they are what this project's
users actually meet, and no foreign ruleset covers them.

Fonts and images are new. Fonts read `@font-face`, stylesheet links, the
family and weight a font service was asked for in its query string, and
inline styles; what needs `getComputedStyle` is an optional input the
extension fills rather than something `core` tries to compute, since the
same code runs in a service worker, in Node and in Tauri's WebView. That
distinction is load-bearing in the output too: `usedBy` is only ever
filled from measurement, so its absence means "not measured" and never
"unused".

Images cover `<img>`, `srcset`, `<picture><source>`, CSS backgrounds,
preloads, icons and social previews, resolved against `<base href>` where
the document sets one, and merged by absolute URL — a hero image is
routinely preloaded, given as `src` and listed in `srcset` four times, and
a list that repeated it five times would be complete and useless. Icons
and Open Graph previews keep their own origin kind so a "download all"
does not offer a 32px favicon alongside the product photographs.

`parseSrcset` is deliberately not a comma split. A resizing CDN writes
`/c_fill,w_300,h_300/pic.jpg`, and splitting on commas turns one product
image into two broken URLs; HTML's own parse takes the URL as a run of
non-whitespace and only then looks for a descriptor, so that is what this
does. There is a test with a comma-bearing CDN URL to keep it that way.

Nothing here fetches, which is what lets the whole inspector run in a
popup without a request against the shop's server and without touching
§10's politeness accounting. Where a value can only be known by
downloading the asset — byte size, natural dimensions — the field is
absent rather than guessed. Empty results are treated as answers
throughout, and tested as such: a hand-written page genuinely uses no
recognised technology, and a confident wrong answer is the failure mode
that matters most in a tool people read as a fact about a shop.

762 tests still pass; 38 new ones cover the phase's checklist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repository is public and the README is what people read first, so
three things in it were wrong or missing after phase 13.

The test badge said 760, which was already stale before this phase and is
now 800. The packages table did not mention the inspector at all, so the
only place `core`'s new surface appeared was a row in the phase table.

The important one is the wording: phase 13 built an engine and no UI, so
the Status section now says plainly that the inspector is not yet
something you can click and names the phase that changes that. A public
README that implied a working feature here would be advertising something
nobody can use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hami9
hami9 merged commit 4d0f59c into main Aug 24, 2026
1 check passed
@hami9
hami9 deleted the feat/phase-13-inspector-engine branch August 24, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant