@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,476 @@
1
+ /**
2
+ * ESI Translation Observer
3
+ *
4
+ * MutationObserver-based system for automatically translating elements
5
+ * decorated with data-translate attribute.
6
+ *
7
+ * @example
8
+ * ```html
9
+ * <!-- Basic translation -->
10
+ * <p data-translate data-target-lang="es">Hello world</p>
11
+ *
12
+ * <!-- With source language hint -->
13
+ * <span data-translate data-source-lang="en" data-target-lang="fr">Welcome</span>
14
+ *
15
+ * <!-- With translation context -->
16
+ * <p data-translate data-translate-context="formal, business">
17
+ * Please review the attached document.
18
+ * </p>
19
+ * ```
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // React hook usage
24
+ * function App() {
25
+ * useTranslationObserver({
26
+ * translateAttribute: 'data-translate',
27
+ * batchSize: 10,
28
+ * debounceMs: 100,
29
+ * });
30
+ *
31
+ * return <div data-translate data-target-lang="es">Hello</div>;
32
+ * }
33
+ * ```
34
+ */
35
+
36
+ import { useEffect, useRef } from 'react';
37
+ import type { TranslationResult } from './types';
38
+ import {
39
+ translateWithAIGateway,
40
+ detectTargetLanguage,
41
+ normalizeLanguageCode,
42
+ } from './esi-translate';
43
+
44
+ // ============================================================================
45
+ // Types
46
+ // ============================================================================
47
+
48
+ export interface TranslationObserverConfig {
49
+ /** Root element to observe (defaults to document.body) */
50
+ root?: Element;
51
+
52
+ /** Attribute that marks elements for translation (default: 'data-translate') */
53
+ translateAttribute?: string;
54
+
55
+ /** Batch translations for efficiency (default: 10) */
56
+ batchSize?: number;
57
+
58
+ /** Debounce time for mutations in ms (default: 100) */
59
+ debounceMs?: number;
60
+
61
+ /** AI Gateway endpoint */
62
+ endpoint?: string;
63
+
64
+ /** Default target language if not specified on element */
65
+ defaultTargetLanguage?: string;
66
+
67
+ /** Cache TTL in seconds (default: 86400 = 24 hours) */
68
+ cacheTtl?: number;
69
+
70
+ /** Callback when element is translated */
71
+ onTranslate?: (element: Element, result: TranslationResult) => void;
72
+
73
+ /** Callback on translation error */
74
+ onError?: (element: Element, error: string) => void;
75
+ }
76
+
77
+ interface TranslationQueueItem {
78
+ element: Element;
79
+ originalText: string;
80
+ targetLanguage: string;
81
+ sourceLanguage: string;
82
+ context?: string;
83
+ }
84
+
85
+ // ============================================================================
86
+ // TranslationObserver Class
87
+ // ============================================================================
88
+
89
+ /**
90
+ * TranslationObserver - Watches DOM for translatable elements
91
+ *
92
+ * Uses MutationObserver to detect elements with data-translate attribute
93
+ * and automatically translates them using the AI Gateway.
94
+ */
95
+ export class TranslationObserver {
96
+ private observer: MutationObserver | null = null;
97
+ private config: Required<
98
+ Omit<TranslationObserverConfig, 'root' | 'onTranslate' | 'onError'>
99
+ > & {
100
+ root: Element | null;
101
+ onTranslate?: TranslationObserverConfig['onTranslate'];
102
+ onError?: TranslationObserverConfig['onError'];
103
+ };
104
+ private translationQueue: TranslationQueueItem[] = [];
105
+ private isProcessing = false;
106
+ private debounceTimer: ReturnType<typeof setTimeout> | null = null;
107
+ private translatedElements = new WeakSet<Element>();
108
+
109
+ constructor(config: TranslationObserverConfig = {}) {
110
+ this.config = {
111
+ root: config.root ?? null,
112
+ translateAttribute: config.translateAttribute ?? 'data-translate',
113
+ batchSize: config.batchSize ?? 10,
114
+ debounceMs: config.debounceMs ?? 100,
115
+ endpoint: config.endpoint ?? 'https://ai-gateway.taylorbuley.workers.dev',
116
+ defaultTargetLanguage: config.defaultTargetLanguage ?? 'en',
117
+ cacheTtl: config.cacheTtl ?? 86400,
118
+ onTranslate: config.onTranslate,
119
+ onError: config.onError,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Start observing for translatable elements
125
+ */
126
+ observe(): void {
127
+ if (
128
+ typeof window === 'undefined' ||
129
+ typeof MutationObserver === 'undefined'
130
+ ) {
131
+ console.warn('[TranslationObserver] MutationObserver not available');
132
+ return;
133
+ }
134
+
135
+ const root = this.config.root ?? document.body;
136
+ if (!root) {
137
+ console.warn('[TranslationObserver] Root element not found');
138
+ return;
139
+ }
140
+
141
+ // Create observer
142
+ this.observer = new MutationObserver((mutations) => {
143
+ this.handleMutations(mutations);
144
+ });
145
+
146
+ // Start observing
147
+ this.observer.observe(root, {
148
+ childList: true,
149
+ subtree: true,
150
+ characterData: true,
151
+ attributes: true,
152
+ attributeFilter: [this.config.translateAttribute, 'data-target-lang'],
153
+ });
154
+
155
+ // Translate existing elements
156
+ this.translateAll();
157
+ }
158
+
159
+ /**
160
+ * Stop observing
161
+ */
162
+ disconnect(): void {
163
+ if (this.observer) {
164
+ this.observer.disconnect();
165
+ this.observer = null;
166
+ }
167
+
168
+ if (this.debounceTimer) {
169
+ clearTimeout(this.debounceTimer);
170
+ this.debounceTimer = null;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Manually translate all current elements
176
+ */
177
+ async translateAll(): Promise<void> {
178
+ const root = this.config.root ?? document.body;
179
+ if (!root) return;
180
+
181
+ const elements = root.querySelectorAll(
182
+ `[${this.config.translateAttribute}]`,
183
+ );
184
+ Array.from(elements).forEach((element) => {
185
+ if (!this.translatedElements.has(element)) {
186
+ this.queueElement(element);
187
+ }
188
+ });
189
+
190
+ await this.processQueue();
191
+ }
192
+
193
+ /**
194
+ * Translate a specific element
195
+ */
196
+ async translateElement(element: Element): Promise<TranslationResult | null> {
197
+ const originalText = element.textContent?.trim();
198
+ if (!originalText) return null;
199
+
200
+ const targetLanguage = this.getTargetLanguage(element);
201
+ const sourceLanguage = this.getSourceLanguage(element);
202
+ const context = element.getAttribute('data-translate-context') ?? undefined;
203
+
204
+ try {
205
+ const result = await translateWithAIGateway(
206
+ originalText,
207
+ targetLanguage,
208
+ {
209
+ sourceLanguage,
210
+ context,
211
+ endpoint: this.config.endpoint,
212
+ },
213
+ );
214
+
215
+ // Update element content
216
+ if (result.translated !== originalText) {
217
+ element.textContent = result.translated;
218
+ element.setAttribute('data-translated', 'true');
219
+ element.setAttribute('data-original-text', originalText);
220
+ }
221
+
222
+ this.translatedElements.add(element);
223
+ this.config.onTranslate?.(element, result);
224
+
225
+ return result;
226
+ } catch (error) {
227
+ const errorMsg =
228
+ error instanceof Error ? error.message : 'Translation failed';
229
+ this.config.onError?.(element, errorMsg);
230
+ return null;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Handle DOM mutations
236
+ */
237
+ private handleMutations(mutations: MutationRecord[]): void {
238
+ let hasNewElements = false;
239
+
240
+ for (const mutation of mutations) {
241
+ if (mutation.type === 'childList') {
242
+ // Check added nodes
243
+ Array.from(mutation.addedNodes).forEach((node) => {
244
+ if (node instanceof Element) {
245
+ if (node.hasAttribute(this.config.translateAttribute)) {
246
+ if (!this.translatedElements.has(node)) {
247
+ this.queueElement(node);
248
+ hasNewElements = true;
249
+ }
250
+ }
251
+ // Check descendants
252
+ const descendants = node.querySelectorAll(
253
+ `[${this.config.translateAttribute}]`,
254
+ );
255
+ Array.from(descendants).forEach((descendant) => {
256
+ if (!this.translatedElements.has(descendant)) {
257
+ this.queueElement(descendant);
258
+ hasNewElements = true;
259
+ }
260
+ });
261
+ }
262
+ });
263
+ } else if (mutation.type === 'attributes') {
264
+ // Element got data-translate attribute added
265
+ if (mutation.target instanceof Element) {
266
+ if (mutation.target.hasAttribute(this.config.translateAttribute)) {
267
+ if (!this.translatedElements.has(mutation.target)) {
268
+ this.queueElement(mutation.target);
269
+ hasNewElements = true;
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ if (hasNewElements) {
277
+ this.debouncedProcessQueue();
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Queue an element for translation
283
+ */
284
+ private queueElement(element: Element): void {
285
+ const originalText = element.textContent?.trim();
286
+ if (!originalText) return;
287
+
288
+ // Skip if already in queue
289
+ if (this.translationQueue.some((item) => item.element === element)) {
290
+ return;
291
+ }
292
+
293
+ const targetLanguage = this.getTargetLanguage(element);
294
+ const sourceLanguage = this.getSourceLanguage(element);
295
+ const context = element.getAttribute('data-translate-context') ?? undefined;
296
+
297
+ this.translationQueue.push({
298
+ element,
299
+ originalText,
300
+ targetLanguage,
301
+ sourceLanguage,
302
+ context,
303
+ });
304
+ }
305
+
306
+ /**
307
+ * Debounced queue processing
308
+ */
309
+ private debouncedProcessQueue(): void {
310
+ if (this.debounceTimer) {
311
+ clearTimeout(this.debounceTimer);
312
+ }
313
+
314
+ this.debounceTimer = setTimeout(() => {
315
+ this.processQueue();
316
+ }, this.config.debounceMs);
317
+ }
318
+
319
+ /**
320
+ * Process queued translations
321
+ */
322
+ private async processQueue(): Promise<void> {
323
+ if (this.isProcessing || this.translationQueue.length === 0) {
324
+ return;
325
+ }
326
+
327
+ this.isProcessing = true;
328
+
329
+ try {
330
+ // Process in batches
331
+ while (this.translationQueue.length > 0) {
332
+ const batch = this.translationQueue.splice(0, this.config.batchSize);
333
+
334
+ // Process batch concurrently
335
+ await Promise.all(
336
+ batch.map(async (item) => {
337
+ try {
338
+ const result = await translateWithAIGateway(
339
+ item.originalText,
340
+ item.targetLanguage,
341
+ {
342
+ sourceLanguage: item.sourceLanguage,
343
+ context: item.context,
344
+ endpoint: this.config.endpoint,
345
+ },
346
+ );
347
+
348
+ // Update element content
349
+ if (result.translated !== item.originalText) {
350
+ item.element.textContent = result.translated;
351
+ item.element.setAttribute('data-translated', 'true');
352
+ item.element.setAttribute(
353
+ 'data-original-text',
354
+ item.originalText,
355
+ );
356
+ }
357
+
358
+ this.translatedElements.add(item.element);
359
+ this.config.onTranslate?.(item.element, result);
360
+ } catch (error) {
361
+ const errorMsg =
362
+ error instanceof Error ? error.message : 'Translation failed';
363
+ this.config.onError?.(item.element, errorMsg);
364
+ }
365
+ }),
366
+ );
367
+ }
368
+ } finally {
369
+ this.isProcessing = false;
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Get target language for an element
375
+ */
376
+ private getTargetLanguage(element: Element): string {
377
+ const explicit = element.getAttribute('data-target-lang');
378
+ if (explicit) return normalizeLanguageCode(explicit);
379
+
380
+ return detectTargetLanguage(undefined, undefined);
381
+ }
382
+
383
+ /**
384
+ * Get source language for an element
385
+ */
386
+ private getSourceLanguage(element: Element): string {
387
+ const explicit = element.getAttribute('data-source-lang');
388
+ if (explicit) return normalizeLanguageCode(explicit);
389
+
390
+ return 'auto';
391
+ }
392
+ }
393
+
394
+ // ============================================================================
395
+ // React Hook
396
+ // ============================================================================
397
+
398
+ /**
399
+ * React hook for translation observer
400
+ *
401
+ * @example
402
+ * ```tsx
403
+ * function App() {
404
+ * useTranslationObserver({
405
+ * translateAttribute: 'data-translate',
406
+ * onTranslate: (element, result) => {
407
+ * console.log('Translated:', result.translated);
408
+ * },
409
+ * });
410
+ *
411
+ * return (
412
+ * <div>
413
+ * <p data-translate data-target-lang="es">Hello world</p>
414
+ * <p data-translate data-target-lang="fr">Goodbye</p>
415
+ * </div>
416
+ * );
417
+ * }
418
+ * ```
419
+ */
420
+ export function useTranslationObserver(
421
+ config?: TranslationObserverConfig,
422
+ ): React.RefObject<TranslationObserver | null> {
423
+ const observerRef = useRef<TranslationObserver | null>(null);
424
+
425
+ useEffect(() => {
426
+ observerRef.current = new TranslationObserver(config);
427
+ observerRef.current.observe();
428
+
429
+ return () => {
430
+ observerRef.current?.disconnect();
431
+ };
432
+ }, []);
433
+
434
+ return observerRef;
435
+ }
436
+
437
+ // ============================================================================
438
+ // Auto-initialization
439
+ // ============================================================================
440
+
441
+ /**
442
+ * Auto-initialize translation observer when DOM is ready
443
+ *
444
+ * Call this in your app's entry point to automatically translate
445
+ * all elements with data-translate attribute.
446
+ *
447
+ * @example
448
+ * ```tsx
449
+ * // In your app entry point
450
+ * import { initTranslationObserver } from '@affectively/aeon-flux';
451
+ *
452
+ * initTranslationObserver({
453
+ * defaultTargetLanguage: 'es',
454
+ * onTranslate: (element, result) => {
455
+ * console.log('Translated:', result.translated);
456
+ * },
457
+ * });
458
+ * ```
459
+ */
460
+ export function initTranslationObserver(
461
+ config?: TranslationObserverConfig,
462
+ ): TranslationObserver {
463
+ const observer = new TranslationObserver(config);
464
+
465
+ if (typeof document !== 'undefined') {
466
+ if (document.readyState === 'loading') {
467
+ document.addEventListener('DOMContentLoaded', () => {
468
+ observer.observe();
469
+ });
470
+ } else {
471
+ observer.observe();
472
+ }
473
+ }
474
+
475
+ return observer;
476
+ }