@affectively/aeon-pages 1.3.0

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,296 @@
1
+ /**
2
+ * React Hook for Aeon Analytics
3
+ *
4
+ * Initializes GTM, syncs ESI context, and starts click tracking.
5
+ * One hook to rule them all.
6
+ */
7
+
8
+ import { useEffect, useRef, useCallback } from 'react';
9
+ import type { AnalyticsConfig, MerkleTree } from './types';
10
+ import { initializeGTM, waitForGTM } from './gtm-loader';
11
+ import { initContextBridge, pushPageView, getESIState } from './context-bridge';
12
+ import {
13
+ initClickTracker,
14
+ trackClick,
15
+ trackInteraction,
16
+ } from './click-tracker';
17
+ import { setDebugMode } from './data-layer';
18
+
19
+ // ============================================================================
20
+ // Hook Return Type
21
+ // ============================================================================
22
+
23
+ export interface UseAnalyticsReturn {
24
+ /** Whether analytics is initialized */
25
+ isInitialized: boolean;
26
+
27
+ /** Whether GTM is loaded and ready */
28
+ isGTMReady: boolean;
29
+
30
+ /** Manually track a click on an element */
31
+ trackClick: (element: HTMLElement, event?: MouseEvent) => void;
32
+
33
+ /** Track a custom interaction */
34
+ trackInteraction: (
35
+ name: string,
36
+ data: Record<string, unknown>,
37
+ element?: HTMLElement,
38
+ ) => void;
39
+
40
+ /** Manually push a page view */
41
+ pushPageView: (merkleRoot?: string) => void;
42
+
43
+ /** Set the current Merkle tree (for page view tracking) */
44
+ setMerkleTree: (tree: MerkleTree) => void;
45
+ }
46
+
47
+ // ============================================================================
48
+ // Main Hook
49
+ // ============================================================================
50
+
51
+ /**
52
+ * Initialize Aeon Analytics with automatic click tracking
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * function App() {
57
+ * useAeonAnalytics({
58
+ * gtmContainerId: 'GTM-XXXXXX',
59
+ * trackClicks: true,
60
+ * syncESIContext: true,
61
+ * });
62
+ *
63
+ * return <div>My App</div>;
64
+ * }
65
+ * ```
66
+ */
67
+ export function useAeonAnalytics(config: AnalyticsConfig): UseAnalyticsReturn {
68
+ const initializedRef = useRef(false);
69
+ const gtmReadyRef = useRef(false);
70
+ const cleanupRef = useRef<(() => void) | null>(null);
71
+ const merkleTreeRef = useRef<MerkleTree | null>(null);
72
+ const configRef = useRef(config);
73
+
74
+ // Keep config ref updated
75
+ configRef.current = config;
76
+
77
+ // Initialize on mount
78
+ useEffect(() => {
79
+ // Prevent double initialization
80
+ if (initializedRef.current || window.__AEON_ANALYTICS_INITIALIZED__) {
81
+ return;
82
+ }
83
+
84
+ initializedRef.current = true;
85
+ window.__AEON_ANALYTICS_INITIALIZED__ = true;
86
+
87
+ // Enable debug mode if configured
88
+ if (config.debug) {
89
+ setDebugMode(true);
90
+ }
91
+
92
+ const cleanupFunctions: (() => void)[] = [];
93
+
94
+ // 1. Initialize GTM
95
+ if (config.gtmContainerId) {
96
+ initializeGTM({
97
+ containerId: config.gtmContainerId,
98
+ dataLayerName: config.dataLayerName,
99
+ });
100
+
101
+ // Wait for GTM to be ready
102
+ waitForGTM().then((ready) => {
103
+ gtmReadyRef.current = ready;
104
+ });
105
+ }
106
+
107
+ // 2. Initialize ESI context bridge
108
+ if (config.syncESIContext !== false) {
109
+ const cleanup = initContextBridge({
110
+ dataLayerName: config.dataLayerName,
111
+ eventPrefix: config.eventPrefix,
112
+ syncESIContext: config.syncESIContext,
113
+ });
114
+ cleanupFunctions.push(cleanup);
115
+ }
116
+
117
+ // 3. Initialize click tracking
118
+ if (config.trackClicks !== false) {
119
+ const cleanup = initClickTracker(config);
120
+ cleanupFunctions.push(cleanup);
121
+ }
122
+
123
+ // 4. Push initial page view
124
+ if (config.trackPageViews !== false) {
125
+ const merkleRoot = merkleTreeRef.current?.rootHash || '';
126
+ pushPageView(
127
+ {
128
+ dataLayerName: config.dataLayerName,
129
+ eventPrefix: config.eventPrefix,
130
+ },
131
+ merkleRoot,
132
+ );
133
+ }
134
+
135
+ // Store cleanup function
136
+ cleanupRef.current = () => {
137
+ for (const cleanup of cleanupFunctions) {
138
+ cleanup();
139
+ }
140
+ window.__AEON_ANALYTICS_INITIALIZED__ = false;
141
+ };
142
+
143
+ // Cleanup on unmount
144
+ return () => {
145
+ if (cleanupRef.current) {
146
+ cleanupRef.current();
147
+ cleanupRef.current = null;
148
+ }
149
+ initializedRef.current = false;
150
+ };
151
+ }, []); // Only run once on mount
152
+
153
+ // Handle route changes
154
+ useEffect(() => {
155
+ if (!initializedRef.current || config.trackPageViews === false) {
156
+ return;
157
+ }
158
+
159
+ // Push page view on pathname change
160
+ const merkleRoot = merkleTreeRef.current?.rootHash || '';
161
+ pushPageView(
162
+ {
163
+ dataLayerName: config.dataLayerName,
164
+ eventPrefix: config.eventPrefix,
165
+ },
166
+ merkleRoot,
167
+ );
168
+ }, [typeof window !== 'undefined' ? window.location.pathname : '']);
169
+
170
+ // Manual track click
171
+ const handleTrackClick = useCallback(
172
+ (element: HTMLElement, event?: MouseEvent) => {
173
+ trackClick(element, event, {
174
+ dataLayerName: configRef.current.dataLayerName,
175
+ eventPrefix: configRef.current.eventPrefix,
176
+ clickOptions: configRef.current.clickOptions,
177
+ });
178
+ },
179
+ [],
180
+ );
181
+
182
+ // Manual track interaction
183
+ const handleTrackInteraction = useCallback(
184
+ (name: string, data: Record<string, unknown>, element?: HTMLElement) => {
185
+ trackInteraction(name, data, element, {
186
+ dataLayerName: configRef.current.dataLayerName,
187
+ eventPrefix: configRef.current.eventPrefix,
188
+ });
189
+ },
190
+ [],
191
+ );
192
+
193
+ // Manual push page view
194
+ const handlePushPageView = useCallback((merkleRoot?: string) => {
195
+ pushPageView(
196
+ {
197
+ dataLayerName: configRef.current.dataLayerName,
198
+ eventPrefix: configRef.current.eventPrefix,
199
+ },
200
+ merkleRoot || merkleTreeRef.current?.rootHash || '',
201
+ );
202
+ }, []);
203
+
204
+ // Set Merkle tree
205
+ const handleSetMerkleTree = useCallback((tree: MerkleTree) => {
206
+ merkleTreeRef.current = tree;
207
+ window.__AEON_MERKLE_TREE__ = tree;
208
+ }, []);
209
+
210
+ return {
211
+ isInitialized: initializedRef.current,
212
+ isGTMReady: gtmReadyRef.current,
213
+ trackClick: handleTrackClick,
214
+ trackInteraction: handleTrackInteraction,
215
+ pushPageView: handlePushPageView,
216
+ setMerkleTree: handleSetMerkleTree,
217
+ };
218
+ }
219
+
220
+ // ============================================================================
221
+ // Utility Hooks
222
+ // ============================================================================
223
+
224
+ /**
225
+ * Get current ESI state in a component
226
+ */
227
+ export function useESIState() {
228
+ return getESIState();
229
+ }
230
+
231
+ /**
232
+ * Track when a component mounts/unmounts
233
+ */
234
+ export function useTrackMount(
235
+ componentName: string,
236
+ config?: Pick<AnalyticsConfig, 'dataLayerName' | 'eventPrefix'>,
237
+ ) {
238
+ useEffect(() => {
239
+ trackInteraction(
240
+ 'component.mount',
241
+ { component: componentName },
242
+ undefined,
243
+ config,
244
+ );
245
+
246
+ return () => {
247
+ trackInteraction(
248
+ 'component.unmount',
249
+ { component: componentName },
250
+ undefined,
251
+ config,
252
+ );
253
+ };
254
+ }, [componentName]);
255
+ }
256
+
257
+ /**
258
+ * Track visibility of an element using IntersectionObserver
259
+ */
260
+ export function useTrackVisibility(
261
+ ref: React.RefObject<HTMLElement>,
262
+ componentName: string,
263
+ config?: Pick<AnalyticsConfig, 'dataLayerName' | 'eventPrefix'>,
264
+ ) {
265
+ const hasTracked = useRef(false);
266
+
267
+ useEffect(() => {
268
+ if (!ref.current || hasTracked.current) {
269
+ return;
270
+ }
271
+
272
+ const observer = new IntersectionObserver(
273
+ (entries) => {
274
+ for (const entry of entries) {
275
+ if (entry.isIntersecting && !hasTracked.current) {
276
+ hasTracked.current = true;
277
+ trackInteraction(
278
+ 'component.visible',
279
+ { component: componentName },
280
+ ref.current || undefined,
281
+ config,
282
+ );
283
+ observer.disconnect();
284
+ }
285
+ }
286
+ },
287
+ { threshold: 0.5 },
288
+ );
289
+
290
+ observer.observe(ref.current);
291
+
292
+ return () => {
293
+ observer.disconnect();
294
+ };
295
+ }, [componentName]);
296
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ESNext", "DOM"],
7
+ "declaration": true,
8
+ "declarationDir": "./dist",
9
+ "emitDeclarationOnly": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "esModuleInterop": true,
13
+ "jsx": "react-jsx",
14
+ "outDir": "./dist",
15
+ "rootDir": "./src"
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
19
+ }