@wireai/activation 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +82 -40
- package/CHANGELOG.md +17 -2
- package/INTEGRATION_PROMPT.md +8 -8
- package/README.md +38 -38
- package/dist/analytics/index.d.mts +92 -0
- package/dist/analytics/index.d.ts +92 -0
- package/dist/analytics/index.js +150 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/index.mjs +139 -0
- package/dist/analytics/index.mjs.map +1 -0
- package/dist/analyticsEvent-B8v3BZjM.d.mts +419 -0
- package/dist/analyticsEvent-DvjB92kK.d.ts +419 -0
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/decision-BzbiKwk3.d.mts +79 -0
- package/dist/decision-plDEOCkt.d.ts +79 -0
- package/dist/index.d.mts +8 -418
- package/dist/index.d.ts +8 -418
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.d.mts +2 -1
- package/dist/questionnaire/index.d.ts +2 -1
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.d.mts +5 -40
- package/dist/reviews/index.d.ts +5 -40
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs.map +1 -1
- package/dist/transport-BeO_Brcu.d.mts +40 -0
- package/dist/transport-DLpd1v5_.d.ts +40 -0
- package/dist/{decision-Cl8OFYzu.d.mts → types-A6pTxIZV.d.mts} +1 -77
- package/dist/{decision-CFvGY6nP.d.ts → types-BhpXJGlg.d.ts} +1 -77
- package/llms.txt +7 -7
- package/metro/index.d.ts +3 -3
- package/metro/index.js +3 -3
- package/package.json +15 -1
- package/src/analytics/index.ts +39 -0
- package/src/analytics/screenTracking.ts +122 -0
- package/src/analytics/useScreenTracking.ts +48 -0
- package/src/coachmarks/index.ts +2 -2
- package/src/features/WireFeaturesProvider.tsx +1 -1
- package/src/features/index.ts +2 -2
- package/src/index.ts +1 -1
- package/src/questionnaire/index.ts +2 -2
- package/src/reviews/index.ts +2 -2
- package/src/showcase/index.ts +2 -2
- package/src/types.ts +1 -1
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# wireai
|
|
1
|
+
# @wireai/activation
|
|
2
2
|
|
|
3
3
|
> A drop-in AI onboarding kit for React Native / Expo. One component, `<WireOnboarding>`, runs an
|
|
4
4
|
> entire backend-orchestrated, AI-driven onboarding flow (themed cards, progress bar, a mid-flow
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
## What an agent needs to know
|
|
10
10
|
|
|
11
|
-
- **Install:** `npm install wireai
|
|
12
|
-
- **Metro (required):** `module.exports = withWireOnboarding(getDefaultConfig(__dirname))` from
|
|
11
|
+
- **Install:** `npm install @wireai/activation wireai-rn` (react + react-native are peers).
|
|
12
|
+
- **Metro (required):** `module.exports = withWireOnboarding(getDefaultConfig(__dirname))` from `@wireai/activation/metro`, which pins one copy of react/react-native/wireai-rn/zod (prevents the dual-React crash).
|
|
13
13
|
- **Two secrets:** an app `apiKey` (a `wai_…` key that resolves the tenant server-side) and the backend `serverUrl`. Created in the getwireai console or via the backend's `register_<app>.py`. Nothing renders without both.
|
|
14
14
|
- **Render:** drop `<WireOnboarding config={wireConfigFromEnv({ appId })} theme={...} onComplete={persist} fallbackFlow={<YourStaticOnboarding/>} />` into the signup flow.
|
|
15
|
-
- **Capture is deterministic
|
|
16
|
-
- **It can never break onboarding
|
|
15
|
+
- **Capture is deterministic.** `onComplete` returns `{ answers, raw }`; no second LLM call. Persist all answers through the app's normal profile-update path.
|
|
16
|
+
- **It can never break onboarding.** Pass `fallbackFlow` (your existing static onboarding) so a backend error/timeout degrades instead of dead-ending.
|
|
17
17
|
|
|
18
18
|
## Helpers (the reusable substrate)
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- `isOnboardingEnabled({ remote? })` → the standard gate (transport present: apiKey + serverUrl). No on/off env flag; `remote` is an optional kill-switch the app passes in.
|
|
22
22
|
- `WIRE_ONBOARDING_EVENTS` + `toAnalyticsEvent(event)` → canonical `wire_onboarding_*` funnel names; log via the app's own analytics.
|
|
23
23
|
- `attributionMetadata(a)` → shape install/ad attribution into `config.metadata` (forwarded to the agent).
|
|
24
|
-
- `reportClientEvent` / `makeSessionId` → report device-only funnel events
|
|
24
|
+
- `reportClientEvent(target, event)` / `reportClientEvents` / `makeSessionId` (ROOT-exported, not a subpath) → report device-only funnel events. Contract: `POST {serverUrl}/v1/events`, header `Authorization: Bearer {apiKey}`, body `{ "events": [ ... ] }`; `target = { serverUrl, apiKey }` from the config. `<WireOnboarding>` does this automatically: `dropped` on unmount-without-complete, `client_fallback` on degrade-to-static. Hosts must not double-report fallback.
|
|
25
25
|
- `deriveAnswers(messages)`, `themeFromBrand({ primary })`, `defaultIllustrations`, `DemoOnboarding` (dev/QA, no account).
|
|
26
26
|
|
|
27
27
|
## Files
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
|
|
34
34
|
## Fastest path
|
|
35
35
|
|
|
36
|
-
Run the `wire-rn-integration` Claude skill
|
|
36
|
+
Run the `wire-rn-integration` Claude skill. It detects the app's conventions, wires Metro, derives a theme, scaffolds the screen, wires a fallback, and type-checks. Otherwise use INTEGRATION_PROMPT.md.
|
package/metro/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Type declarations for the
|
|
2
|
+
* Type declarations for the `@wireai/activation/metro` helper. This module runs in
|
|
3
3
|
* Node inside metro.config.js, so the config is loosely typed (`any`-ish) to avoid
|
|
4
4
|
* coupling to a specific Metro/Expo version.
|
|
5
5
|
*/
|
|
@@ -11,7 +11,7 @@ export interface WithWireOnboardingOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
appRoot?: string;
|
|
13
13
|
/**
|
|
14
|
-
* Absolute path to the kit's `src/` dir. When set, the
|
|
14
|
+
* Absolute path to the kit's `src/` dir. When set, the `@wireai/activation`
|
|
15
15
|
* specifier maps here (source consumption) and the dir is added to watchFolders.
|
|
16
16
|
* When omitted, the kit resolves from the installed package.
|
|
17
17
|
*/
|
|
@@ -19,7 +19,7 @@ export interface WithWireOnboardingOptions {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Non-destructively wire the wireai
|
|
22
|
+
* Non-destructively wire the @wireai/activation kit into an existing Metro config:
|
|
23
23
|
* pins single-instance deps to the app's copy, optionally maps the kit specifier
|
|
24
24
|
* to source + watches it. Mutates and returns the same config object.
|
|
25
25
|
*/
|
package/metro/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* withWireOnboarding — one-call Metro wiring for the wireai
|
|
2
|
+
* withWireOnboarding — one-call Metro wiring for the @wireai/activation kit.
|
|
3
3
|
*
|
|
4
4
|
* This file runs in Node inside your app's `metro.config.js` (it is NOT React
|
|
5
5
|
* Native code), so it's plain CommonJS with zero RN imports.
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
*
|
|
11
11
|
* // External app (kit installed from git/tarball):
|
|
12
12
|
* const { getDefaultConfig } = require('expo/metro-config');
|
|
13
|
-
* const { withWireOnboarding } = require('wireai
|
|
13
|
+
* const { withWireOnboarding } = require('@wireai/activation/metro');
|
|
14
14
|
* module.exports = withWireOnboarding(getDefaultConfig(__dirname));
|
|
15
15
|
*
|
|
16
16
|
* // Source consumer (monorepo app, kit resolved from its src/):
|
|
17
17
|
* const path = require('path');
|
|
18
18
|
* module.exports = withWireOnboarding(getDefaultConfig(__dirname), {
|
|
19
|
-
* source: path.join(__dirname, '../../
|
|
19
|
+
* source: path.join(__dirname, '../../wire-activation/src'),
|
|
20
20
|
* });
|
|
21
21
|
*
|
|
22
22
|
* @param {object} config An existing Metro config object (e.g. from getDefaultConfig).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wireai/activation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Premium, fully-themable drop-in AI onboarding kit for React Native / Expo, on top of the open-source wireai-rn SDK.",
|
|
6
6
|
"author": "Malik Chohra <malik@getwireai.com>",
|
|
@@ -46,6 +46,20 @@
|
|
|
46
46
|
"default": "./dist/index.js"
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
+
"./analytics": {
|
|
50
|
+
"react-native": {
|
|
51
|
+
"types": "./dist/analytics/index.d.ts",
|
|
52
|
+
"default": "./src/analytics/index.ts"
|
|
53
|
+
},
|
|
54
|
+
"import": {
|
|
55
|
+
"types": "./dist/analytics/index.d.mts",
|
|
56
|
+
"default": "./dist/analytics/index.mjs"
|
|
57
|
+
},
|
|
58
|
+
"require": {
|
|
59
|
+
"types": "./dist/analytics/index.d.ts",
|
|
60
|
+
"default": "./dist/analytics/index.js"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
49
63
|
"./coachmarks": {
|
|
50
64
|
"react-native": {
|
|
51
65
|
"types": "./dist/coachmarks/index.d.ts",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wireai/activation/analytics — the coherent analytics entry.
|
|
3
|
+
*
|
|
4
|
+
* Subpath export kept OUT of the main barrel so an analytics-only consumer never pulls in the
|
|
5
|
+
* onboarding / showcase / review UI (the kit's tree-shaking guarantee). It bundles the OPT-IN
|
|
6
|
+
* automatic screen-tracking helper with the pre-existing analytics primitives so a host has one
|
|
7
|
+
* import for "report app + onboarding events":
|
|
8
|
+
*
|
|
9
|
+
* import { createScreenTracker, screenTrackingHandler, reportAppEvent } from "@wireai/activation/analytics";
|
|
10
|
+
*
|
|
11
|
+
* The screen-tracking core imports NO navigation library and NO React (the React glue is the thin
|
|
12
|
+
* `useScreenTracking` hook); `reportAppEvent` is pulled from `../reviews/transport` directly, not
|
|
13
|
+
* the `../reviews` barrel, to keep the review UI out of an analytics-only bundle.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ─── Automatic screen tracking (dependency-free core) ─────────────────────────
|
|
17
|
+
export { createScreenTracker, getActiveRouteName, screenTrackingHandler } from "./screenTracking";
|
|
18
|
+
export type {
|
|
19
|
+
ScreenTracker,
|
|
20
|
+
ScreenTrackerOptions,
|
|
21
|
+
NavigationStateLike,
|
|
22
|
+
NavigationRouteLike,
|
|
23
|
+
} from "./screenTracking";
|
|
24
|
+
|
|
25
|
+
// ─── The thin optional React hook over the core ───────────────────────────────
|
|
26
|
+
export { useScreenTracking } from "./useScreenTracking";
|
|
27
|
+
export type { NavigationRefLike } from "./useScreenTracking";
|
|
28
|
+
|
|
29
|
+
// ─── The app-event transport this helper feeds (pure fetch, no review UI) ─────
|
|
30
|
+
export { reportAppEvent } from "../reviews/transport";
|
|
31
|
+
export type { ReportAppEventOptions } from "../reviews/transport";
|
|
32
|
+
|
|
33
|
+
// ─── Device-only onboarding event reporters + session id seed ─────────────────
|
|
34
|
+
export { reportClientEvent, reportClientEvents, makeSessionId } from "./reportClientEvent";
|
|
35
|
+
export type { ClientEvent, ClientEventType, ClientEventTarget } from "./reportClientEvent";
|
|
36
|
+
|
|
37
|
+
// ─── Canonical onboarding funnel names + kit-event mapper ──────────────────────
|
|
38
|
+
export { WIRE_ONBOARDING_EVENTS, toAnalyticsEvent } from "./analyticsEvent";
|
|
39
|
+
export type { WireOnboardingEventName, AnalyticsEvent } from "./analyticsEvent";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* screenTracking — an OPT-IN, dependency-free automatic screen-view helper.
|
|
3
|
+
*
|
|
4
|
+
* Given the host app's navigation STATE (or a nav ref, via the useScreenTracking hook), this
|
|
5
|
+
* emits exactly ONE `screen` app-event per REAL screen change through the kit's existing
|
|
6
|
+
* `reportAppEvent` transport (`POST {serverUrl}/v1/events`, `event_type='app_event'`,
|
|
7
|
+
* `question_key='screen'`, `meta={ screen }`). Param-only changes and re-renders are de-duped
|
|
8
|
+
* away because we resolve and compare the route NAME only — never the params.
|
|
9
|
+
*
|
|
10
|
+
* Dependency-free core: this file imports NO navigation library. React-Navigation / expo-router
|
|
11
|
+
* are host concerns; the host supplies plain state objects and refs, typed structurally here
|
|
12
|
+
* (`NavigationStateLike`). It is also React-free — the optional React glue lives in
|
|
13
|
+
* `useScreenTracking.ts`. `reportAppEvent` is imported from the pure `../reviews/transport`
|
|
14
|
+
* module (NOT the `../reviews` barrel, which would drag the review UI into an analytics-only
|
|
15
|
+
* bundle and defeat tree-shaking).
|
|
16
|
+
*
|
|
17
|
+
* Privacy: only the route NAME ever leaves the device. Route params, query strings, and any
|
|
18
|
+
* user data are never read into the event. Fire-and-forget — this never throws into the UI.
|
|
19
|
+
*/
|
|
20
|
+
import { reportAppEvent } from "../reviews/transport";
|
|
21
|
+
|
|
22
|
+
/** A single route inside a React-Navigation-shaped state (structural — no `@react-navigation`). */
|
|
23
|
+
export interface NavigationRouteLike {
|
|
24
|
+
name: string;
|
|
25
|
+
/** A nested navigator's own state, when this route hosts one. */
|
|
26
|
+
state?: NavigationStateLike;
|
|
27
|
+
/** Route params are intentionally left `unknown` — this helper never reads them. */
|
|
28
|
+
params?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A React-Navigation-shaped navigator state (structural type; no library import). */
|
|
32
|
+
export interface NavigationStateLike {
|
|
33
|
+
/** Index of the active route within `routes`. */
|
|
34
|
+
index?: number;
|
|
35
|
+
routes?: NavigationRouteLike[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Options for {@link createScreenTracker}. */
|
|
39
|
+
export interface ScreenTrackerOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Where to POST. `{ serverUrl, apiKey }` — same shape the kit's review/analytics config
|
|
42
|
+
* exposes. When omitted, the tracker still de-dups and fires `onScreen`, but sends nothing.
|
|
43
|
+
*/
|
|
44
|
+
target?: { serverUrl: string; apiKey: string };
|
|
45
|
+
/** The onboarding/session id to correlate screen views with, when known. */
|
|
46
|
+
sessionId?: string;
|
|
47
|
+
/** A stable, non-PII device id — groups a device's sessions server-side. */
|
|
48
|
+
deviceKey?: string;
|
|
49
|
+
/** Called on every REAL screen change (after de-dup), before the network emit. */
|
|
50
|
+
onScreen?: (screen: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Per-screen filter. Return `false` to skip the NETWORK emit for a screen (last-screen memory
|
|
53
|
+
* is still advanced + `onScreen` still fires) — e.g. to keep a sensitive route out of analytics.
|
|
54
|
+
*/
|
|
55
|
+
shouldTrack?: (screen: string) => boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The screen tracker returned by {@link createScreenTracker}. */
|
|
59
|
+
export interface ScreenTracker {
|
|
60
|
+
/** Report the active screen. Ignores `undefined`/empty and de-dups repeats of the last screen. */
|
|
61
|
+
track: (screen: string | undefined) => void;
|
|
62
|
+
/** Clear the last-screen memory (e.g. on logout) so the next `track` always emits. */
|
|
63
|
+
reset: () => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Walk a React-Navigation-shaped state to the DEEPEST active route and return its NAME (never
|
|
68
|
+
* its params). Recurses `routes[index]` while a nested `.state` exists. Returns `undefined` for
|
|
69
|
+
* a missing/empty/malformed state — the caller treats that as "nothing to report".
|
|
70
|
+
*/
|
|
71
|
+
export const getActiveRouteName = (
|
|
72
|
+
state: NavigationStateLike | undefined,
|
|
73
|
+
): string | undefined => {
|
|
74
|
+
let current: NavigationStateLike | undefined = state;
|
|
75
|
+
let name: string | undefined;
|
|
76
|
+
// Bounded by the finite nesting depth of a real navigator tree.
|
|
77
|
+
while (current && Array.isArray(current.routes) && current.routes.length > 0) {
|
|
78
|
+
const index = typeof current.index === "number" ? current.index : 0;
|
|
79
|
+
const route = current.routes[index];
|
|
80
|
+
if (!route) break;
|
|
81
|
+
name = route.name;
|
|
82
|
+
current = route.state;
|
|
83
|
+
}
|
|
84
|
+
return name;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build a stateful screen tracker. `track` de-dups against the last reported screen so only a
|
|
89
|
+
* REAL change emits; `reset` clears that memory. Fire-and-forget throughout — a missing `target`
|
|
90
|
+
* skips the network but keeps the de-dup + `onScreen` behaviour intact.
|
|
91
|
+
*/
|
|
92
|
+
export const createScreenTracker = (options: ScreenTrackerOptions = {}): ScreenTracker => {
|
|
93
|
+
let lastScreen: string | undefined;
|
|
94
|
+
return {
|
|
95
|
+
track: (screen: string | undefined): void => {
|
|
96
|
+
if (!screen || screen === lastScreen) return;
|
|
97
|
+
lastScreen = screen;
|
|
98
|
+
options.onScreen?.(screen);
|
|
99
|
+
if (options.shouldTrack && !options.shouldTrack(screen)) return;
|
|
100
|
+
reportAppEvent(options.target, "screen", {
|
|
101
|
+
sessionId: options.sessionId,
|
|
102
|
+
deviceKey: options.deviceKey,
|
|
103
|
+
meta: { screen },
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
reset: (): void => {
|
|
107
|
+
lastScreen = undefined;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Adapt a tracker into a React-Navigation `onStateChange` handler — the one-place wiring:
|
|
114
|
+
*
|
|
115
|
+
* <NavigationContainer onStateChange={screenTrackingHandler(tracker)}>
|
|
116
|
+
*
|
|
117
|
+
* It resolves the deepest active route name and hands it to `tracker.track` (which de-dups).
|
|
118
|
+
*/
|
|
119
|
+
export const screenTrackingHandler =
|
|
120
|
+
(tracker: ScreenTracker) =>
|
|
121
|
+
(state: NavigationStateLike | undefined): void =>
|
|
122
|
+
tracker.track(getActiveRouteName(state));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useScreenTracking — a THIN optional React hook over the pure screen-tracking core.
|
|
3
|
+
*
|
|
4
|
+
* It builds one tracker for the component's lifetime and subscribes to the host's navigation
|
|
5
|
+
* ref: it reports the current route on mount and on every `state` event, and unsubscribes on
|
|
6
|
+
* unmount. React is a REQUIRED peer of the kit, so importing it here is allowed; the hook adds
|
|
7
|
+
* NO navigation-library dependency — the ref is typed structurally (`NavigationRefLike`).
|
|
8
|
+
*
|
|
9
|
+
* const navigationRef = useNavigationContainerRef(); // host's @react-navigation ref
|
|
10
|
+
* useScreenTracking(navigationRef, { target, sessionId });
|
|
11
|
+
* // ...<NavigationContainer ref={navigationRef}>
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useRef } from "react";
|
|
14
|
+
|
|
15
|
+
import { createScreenTracker, type ScreenTracker, type ScreenTrackerOptions } from "./screenTracking";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The structural slice of a React-Navigation container ref this hook needs — no
|
|
19
|
+
* `@react-navigation` import. `getCurrentRoute` yields the active route; `addListener("state", …)`
|
|
20
|
+
* fires on every navigation state change and returns its own unsubscribe.
|
|
21
|
+
*/
|
|
22
|
+
export interface NavigationRefLike {
|
|
23
|
+
getCurrentRoute?: () => { name?: string } | undefined;
|
|
24
|
+
addListener?: (type: "state", callback: () => void) => () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Subscribe screen tracking to a host navigation ref. Safe to call with a not-yet-ready ref
|
|
29
|
+
* (the effect no-ops until `addListener` exists). Returns nothing — it wires side effects only.
|
|
30
|
+
*/
|
|
31
|
+
export const useScreenTracking = (
|
|
32
|
+
navigationRef: NavigationRefLike | undefined,
|
|
33
|
+
options: ScreenTrackerOptions = {},
|
|
34
|
+
): void => {
|
|
35
|
+
// One tracker per mount; kept in a ref so re-renders never rebuild the de-dup memory.
|
|
36
|
+
const trackerRef = useRef<ScreenTracker | undefined>(undefined);
|
|
37
|
+
if (!trackerRef.current) trackerRef.current = createScreenTracker(options);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const tracker = trackerRef.current;
|
|
41
|
+
if (!tracker || !navigationRef?.addListener) return;
|
|
42
|
+
const report = (): void => tracker.track(navigationRef.getCurrentRoute?.()?.name);
|
|
43
|
+
report(); // initial screen on mount
|
|
44
|
+
const unsubscribe = navigationRef.addListener("state", report);
|
|
45
|
+
return unsubscribe;
|
|
46
|
+
// Re-subscribe only when the ref identity changes; option changes are read live off the closure.
|
|
47
|
+
}, [navigationRef]);
|
|
48
|
+
};
|
package/src/coachmarks/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* wireai
|
|
2
|
+
* @wireai/activation/coachmarks — the performance-first guided-tour engine.
|
|
3
3
|
*
|
|
4
4
|
* Subpath entry, kept OUT of the main barrel so the core kit stays dependency-
|
|
5
5
|
* free: importing this pulls in the optional peers `react-native-reanimated` and
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* import {
|
|
11
11
|
* CoachmarkProvider, useCoachmarkAnchor, useCoachmarkTour,
|
|
12
|
-
* } from "wireai
|
|
12
|
+
* } from "@wireai/activation/coachmarks";
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// ─── Root provider (mount once, around the NavigationContainer) ───────────────
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* NOTE on the built npm subpaths: React context is shared across the kit's subpath entries in the
|
|
11
11
|
* SOURCE-consumption path (the monorepo apps) and in this test suite — one module instance, one
|
|
12
|
-
* context. If you consume the built
|
|
12
|
+
* context. If you consume the built `@wireai/activation/*` subpaths as separate bundles, pass the
|
|
13
13
|
* resolved flags (or a `featuresConfig`) to each surface explicitly instead of relying on context.
|
|
14
14
|
*/
|
|
15
15
|
import React, { createContext, useContext, useMemo } from "react";
|
package/src/features/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* wireai
|
|
2
|
+
* @wireai/activation feature controls — the per-module dashboard kill switches, consumed by the
|
|
3
3
|
* kit with a HARD fail-open contract (a control-plane outage can never dark your app).
|
|
4
4
|
*
|
|
5
5
|
* These live in the CORE barrel (dependency-free: types + fetch + a React hook/provider). The
|
|
6
6
|
* GATING is wired into the three client surfaces (coachmarks / showcase / reviews); onboarding
|
|
7
7
|
* mode is server-owned, so the kit needs no gating there.
|
|
8
8
|
*
|
|
9
|
-
* import { WireFeaturesProvider, fetchWireFeatures } from "wireai
|
|
9
|
+
* import { WireFeaturesProvider, fetchWireFeatures } from "@wireai/activation";
|
|
10
10
|
*/
|
|
11
11
|
export { fetchWireFeatures } from "./fetchWireFeatures";
|
|
12
12
|
export { useWireFeatures } from "./useWireFeatures";
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* wireai
|
|
2
|
+
* @wireai/activation/questionnaire - the skippable pre-onboarding questionnaire popup.
|
|
3
3
|
*
|
|
4
4
|
* Subpath entry, kept OUT of the main barrel so the core kit stays dependency-free. The gate
|
|
5
5
|
* asks one mandatory question ("what do you think about the app") plus two optional boxes,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* at most once per user per questionnaire id. It reuses the shared `CenteredModal` shell (never
|
|
9
9
|
* importing reviews/ internals for UI).
|
|
10
10
|
*
|
|
11
|
-
* import { QuestionnaireGate, useQuestionnaireGate } from "wireai
|
|
11
|
+
* import { QuestionnaireGate, useQuestionnaireGate } from "@wireai/activation/questionnaire";
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
// ─── The popup UI ───────────────────────────────────────────────────────────────
|
package/src/reviews/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* wireai
|
|
2
|
+
* @wireai/activation/reviews — the in-app review sentiment gate.
|
|
3
3
|
*
|
|
4
4
|
* Subpath entry, kept OUT of the main barrel so the core kit stays dependency-free:
|
|
5
5
|
* importing this pulls in the OPTIONAL peer `expo-store-review` (loaded lazily and only on
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* review and 1-4 to a feather-light feedback form POSTed to the Wire server, and decides
|
|
8
8
|
* WHEN to show via local rules OR a server-provided decision (the AI seam).
|
|
9
9
|
*
|
|
10
|
-
* import { ReviewGate, useReviewGate } from "wireai
|
|
10
|
+
* import { ReviewGate, useReviewGate } from "@wireai/activation/reviews";
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
// ─── The gate UI ──────────────────────────────────────────────────────────────
|
package/src/showcase/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* wireai
|
|
2
|
+
* @wireai/activation/showcase — the pre-onboarding feature showcase ("app intro").
|
|
3
3
|
*
|
|
4
4
|
* Subpath entry, kept OUT of the main barrel so the core kit stays dependency-
|
|
5
5
|
* free: importing this pulls in the optional peer
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `react-native-reanimated`). The app supplies a declarative ShowcaseConfig; the
|
|
8
8
|
* kit bakes in the Wire theme + optional per-slide gesture hand and gates once.
|
|
9
9
|
*
|
|
10
|
-
* import { FeatureShowcase, selectShowcaseSlides } from "wireai
|
|
10
|
+
* import { FeatureShowcase, selectShowcaseSlides } from "@wireai/activation/showcase";
|
|
11
11
|
*/
|
|
12
12
|
export { FeatureShowcase } from "./FeatureShowcase";
|
|
13
13
|
export { selectShowcaseSlides } from "./selectShowcaseSlides";
|
package/src/types.ts
CHANGED