@guideify/js
v0.2.0
Published
Interactive product walkthrough SDK — overlay, spotlight, and step engine.
Readme
@guideify/js
The SDK core for the interactive product-walkthrough platform. Renders the overlay, spotlight, and step cards inside a customer's app, resolves targets at runtime, and reports analytics.
Design rationale for every decision here lives in docs/BUILD_REPORT.md.
Install
<script async src="https://cdn.guideify.in/v1/loader.js" data-guideify-key="pk_live_..."></script>or
npm i @guideify/jsimport { guideify } from '@guideify/js';
guideify.init({ apiKey: 'pk_live_...' });
guideify.identify('user_8f3a', {
email: '[email protected]',
plan: 'pro',
createdAt: '2026-08-01T10:00:00Z', // powers "first 7 days" targeting
});API
| Method | Purpose |
|---|---|
| init(opts) | Boot. Idempotent; safe to call before DOM ready. |
| identify(id, traits, opts?) | Set the user. Re-evaluates flows. |
| group(id, traits) | Company/account traits, addressable as company.*. |
| track(name, props?) | Custom event. Drives event triggers and advanceOn. |
| start(flowId) | Run a flow manually, bypassing triggers and frequency caps. |
| stop() | Tear down the running flow without recording a dismissal. |
| reset() | Clear identity + progress. Call on logout. |
| on(event, cb) | flow_started | flow_completed | flow_dismissed | step_viewed. |
| debug() | Table of every flow and why it did or didn't fire. |
| destroy() | Full teardown; restores patched history methods. |
Architecture
loader.js (0.5 KB gz) → installs a call queue, pulls the core bundle
│
guideify.core.js (26.1 KB gz)
├── core/ init, identity, storage, config fetch, audience rules, event batching
├── targeting/ fingerprint capture, weighted resolver, wait-for-element
├── overlay/ top-layer + shadow root, SVG spotlight, blockers, card, rect tracker
├── engine/ step machine, SPA router patching
└── content/ structured-document renderer (no HTML strings, ever)Element targeting
A recorded CSS selector breaks the next time the customer refactors their CSS,
so we never rely on one. captureFingerprint() records many signals — explicit
id, test id, aria-label, text, ancestor chain, sibling position, geometry — and
resolve() scores every candidate rather than trusting querySelector.
Two rules matter more than the weights:
- Tag mismatch is disqualifying. A
<div>never wins a fingerprint authored against a<button>. - Ambiguity is failure. If the top two candidates score within 0.05, we report no match. Highlighting the wrong element is worse than showing nothing.
Resolutions below CONFIDENCE_STRONG (0.85) emit a target_degraded event, which
is what powers "this step is about to break" alerting in the dashboard.
The paved path is to have the host app opt in:
<button data-guideify-id="new-report">New report</button>That resolves exactly, for free, forever.
Overlay isolation
Three stacked defenses so arbitrary host CSS can't break the UI:
- Top layer via the Popover API — no z-index war, no stacking-context traps.
- Shadow DOM — host CSS can't reach in, ours can't leak out.
all: initialon:hostplus inline!importanton the container.
The shadow root is open, deliberately: closed would prevent customers from
driving the tour in their own Playwright/Cypress suites, which would break their
e2e tests the day they install us.
Visual masking and input blocking are separate mechanisms. An SVG mask cuts the
hole (pointer-events: none); four divs framing the hole do the blocking, so
clicks reach the real target and nowhere else. Non-interactive steps mount one
full-viewport blocker instead.
Development
npm run verify # typecheck → build → test → size budget
npm run demo # host app with a live tour at http://localhost:5173npm run demo serves examples/demo, a deliberately hostile host app: a sticky
header at z-index: 9999, CSS-modules-style hashed class names, an inner scroll
container, and a hash router.
Size budget
Enforced in CI; a build that exceeds it fails.
| Bundle | Budget | Current | |---|---|---| | loader | 2 KB gz | 0.5 KB | | core | 30 KB gz | 26.1 KB |
Design mode's ~200 KB of authoring UI is deliberately not here: it is
packages/builder, fetched from the CDN when guideify.designMode() is called and
handed the live core through BuilderHost.
Status
Implemented and covered by tests or verified in a browser:
- [x] Fingerprint capture + weighted resolver + ambiguity/tag rails
- [x] Wait-for-element with MutationObserver + polling backstop
- [x] Top-layer + shadow-root overlay, SVG spotlight, 1/4-blocker input model
- [x] Rect tracking across scroll, resize, transitions, font swap
- [x] Tooltip positioning (flip/shift/arrow/size), modal fallback
- [x] Step machine: next/back/dismiss, url gating,
target_click,event,element_appears - [x] SPA route detection via
historypatching, restored ondestroy() - [x] Audience rule engine +
explainRulesbehinddebug() - [x] Frequency rules, session/daily caps, progress persistence
- [x] Structured-content renderer, URL sanitization
- [x] Batched analytics with
sendBeaconon hide - [x]
designMode()— fetchespackages/builderon demand, hands it the live core, and answers with what happened rather than only logging it - [x] Visual builder: element picker with live capture grading, and the step editor
- [x] Design-mode handshake — origin-pinned both ways, opened from the dashboard
- [x] Control plane: dashboard, publish pipeline, edge ingest, ClickHouse
- [x] HMAC identity verification, enforced at the edge on both
/stateand ingest - [x]
@guideify/react,@guideify/vueand@guideify/next
Not built yet — see BUILD_REPORT §11:
- [ ] Checklists, hotspots, banners, surveys
- [ ] Targets inside nested iframes
License
Proprietary — see LICENSE. You may bundle this into your own application and serve it to your users; you may not redistribute it on its own.
