@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,295 @@
1
+ /**
2
+ * Tests for HeuristicAdapter
3
+ */
4
+
5
+ import { describe, expect, test, beforeEach } from 'bun:test';
6
+ import { HeuristicAdapter } from './heuristic-adapter';
7
+ import type {
8
+ UserContext,
9
+ ComponentTree,
10
+ ComponentNode,
11
+ ThemeMode,
12
+ } from './types';
13
+
14
+ // Mock component tree
15
+ function createMockTree(nodes: ComponentNode[] = []): ComponentTree {
16
+ const nodeMap = new Map<string, ComponentNode>();
17
+ nodes.forEach((n) => nodeMap.set(n.id, n));
18
+
19
+ if (nodes.length === 0) {
20
+ nodeMap.set('root', { id: 'root', type: 'page' });
21
+ }
22
+
23
+ return {
24
+ rootId: nodes[0]?.id || 'root',
25
+ nodes: nodeMap,
26
+ getNode: (id) => nodeMap.get(id),
27
+ getChildren: () => [],
28
+ getSchema: () => ({
29
+ rootId: nodes[0]?.id || 'root',
30
+ nodeCount: nodeMap.size,
31
+ nodeTypes: [...new Set(nodes.map((n) => n.type))],
32
+ depth: 1,
33
+ }),
34
+ clone: () => createMockTree(nodes),
35
+ };
36
+ }
37
+
38
+ // Mock user context
39
+ function createMockContext(overrides: Partial<UserContext> = {}): UserContext {
40
+ return {
41
+ tier: 'free',
42
+ recentPages: [],
43
+ dwellTimes: new Map(),
44
+ clickPatterns: [],
45
+ preferences: {},
46
+ viewport: { width: 1920, height: 1080 },
47
+ connection: 'fast',
48
+ reducedMotion: false,
49
+ localHour: 12,
50
+ timezone: 'UTC',
51
+ isNewSession: true,
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ describe('HeuristicAdapter', () => {
57
+ let adapter: HeuristicAdapter;
58
+
59
+ beforeEach(() => {
60
+ adapter = new HeuristicAdapter();
61
+ });
62
+
63
+ describe('route()', () => {
64
+ test('returns a valid RouteDecision', async () => {
65
+ const context = createMockContext();
66
+ const tree = createMockTree();
67
+
68
+ const decision = await adapter.route('/', context, tree);
69
+
70
+ expect(decision.route).toBe('/');
71
+ expect(decision.sessionId).toBeDefined();
72
+ expect(decision.routerName).toBe('heuristic');
73
+ expect(decision.confidence).toBeGreaterThan(0);
74
+ expect(decision.routedAt).toBeDefined();
75
+ });
76
+
77
+ test('generates correct density for mobile viewport', async () => {
78
+ const context = createMockContext({
79
+ viewport: { width: 375, height: 667 },
80
+ });
81
+ const tree = createMockTree();
82
+
83
+ const decision = await adapter.route('/', context, tree);
84
+
85
+ expect(decision.density).toBe('compact');
86
+ });
87
+
88
+ test('generates correct density for large desktop', async () => {
89
+ const context = createMockContext({
90
+ viewport: { width: 2560, height: 1440 },
91
+ });
92
+ const tree = createMockTree();
93
+
94
+ const decision = await adapter.route('/', context, tree);
95
+
96
+ expect(decision.density).toBe('comfortable');
97
+ });
98
+
99
+ test('suggests dark theme at night', async () => {
100
+ const context = createMockContext({ localHour: 22 });
101
+ const tree = createMockTree();
102
+
103
+ const decision = await adapter.route('/', context, tree);
104
+
105
+ expect(decision.theme).toBe('dark');
106
+ });
107
+
108
+ test('suggests light theme during day', async () => {
109
+ const context = createMockContext({ localHour: 10 });
110
+ const tree = createMockTree();
111
+
112
+ const decision = await adapter.route('/', context, tree);
113
+
114
+ expect(decision.theme).toBe('light');
115
+ });
116
+
117
+ test('respects explicit theme preference', async () => {
118
+ const context = createMockContext({
119
+ localHour: 22, // Night
120
+ preferences: { theme: 'light' },
121
+ });
122
+ const tree = createMockTree();
123
+
124
+ const decision = await adapter.route('/', context, tree);
125
+
126
+ expect(decision.theme).toBe('light');
127
+ });
128
+
129
+ test('computes prefetch based on connection speed', async () => {
130
+ // Use adapter with multiple default paths so there's something to prefetch
131
+ const adapterWithPaths = new HeuristicAdapter({
132
+ defaultPaths: ['/', '/about', '/contact', '/help'],
133
+ });
134
+ const fastContext = createMockContext({ connection: 'fast' });
135
+ const slowContext = createMockContext({ connection: 'slow-2g' });
136
+ const tree = createMockTree();
137
+
138
+ const fastDecision = await adapterWithPaths.route('/', fastContext, tree);
139
+ const slowDecision = await adapterWithPaths.route('/', slowContext, tree);
140
+
141
+ expect(fastDecision.prefetch?.length).toBeGreaterThan(0);
142
+ expect(slowDecision.prefetch?.length).toBe(0);
143
+ });
144
+ });
145
+
146
+ describe('speculate()', () => {
147
+ test('returns predictions from navigation history', async () => {
148
+ const context = createMockContext({
149
+ recentPages: ['/', '/chat', '/', '/chat', '/', '/settings'],
150
+ });
151
+
152
+ const predictions = await adapter.speculate('/', context);
153
+
154
+ expect(predictions.length).toBeGreaterThan(0);
155
+ expect(predictions).toContain('/chat');
156
+ });
157
+
158
+ test('returns default paths when no history', async () => {
159
+ const adapter = new HeuristicAdapter({
160
+ defaultPaths: ['/home', '/about', '/contact'],
161
+ });
162
+ const context = createMockContext({ recentPages: [] });
163
+
164
+ const predictions = await adapter.speculate('/home', context);
165
+
166
+ expect(predictions).toContain('/about');
167
+ expect(predictions).toContain('/contact');
168
+ expect(predictions).not.toContain('/home'); // Excludes current
169
+ });
170
+ });
171
+
172
+ describe('personalizeTree()', () => {
173
+ test('hides components from hiddenComponents list', () => {
174
+ const tree = createMockTree([
175
+ { id: 'root', type: 'page', children: ['child1', 'child2'] },
176
+ { id: 'child1', type: 'section' },
177
+ { id: 'child2', type: 'section' },
178
+ ]);
179
+
180
+ const decision = {
181
+ route: '/',
182
+ sessionId: 'test',
183
+ hiddenComponents: ['child1'],
184
+ routedAt: Date.now(),
185
+ routerName: 'heuristic',
186
+ confidence: 1,
187
+ };
188
+
189
+ const personalized = adapter.personalizeTree(tree, decision);
190
+ const hiddenNode = personalized.getNode('child1');
191
+
192
+ expect(hiddenNode?.defaultHidden).toBe(true);
193
+ });
194
+ });
195
+
196
+ describe('custom signals', () => {
197
+ test('uses custom deriveAccent', async () => {
198
+ const customAdapter = new HeuristicAdapter({
199
+ signals: {
200
+ deriveAccent: (ctx) =>
201
+ ctx.emotionState?.primary === 'happy' ? '#FFD700' : '#808080',
202
+ },
203
+ });
204
+
205
+ const happyContext = createMockContext({
206
+ emotionState: {
207
+ primary: 'happy',
208
+ valence: 0.8,
209
+ arousal: 0.6,
210
+ confidence: 0.9,
211
+ },
212
+ });
213
+
214
+ const decision = await customAdapter.route(
215
+ '/',
216
+ happyContext,
217
+ createMockTree(),
218
+ );
219
+
220
+ expect(decision.accent).toBe('#FFD700');
221
+ });
222
+
223
+ test('uses custom deriveTheme', async () => {
224
+ const customAdapter = new HeuristicAdapter({
225
+ signals: {
226
+ deriveTheme: (ctx) =>
227
+ ctx.emotionState?.valence && ctx.emotionState.valence < 0
228
+ ? 'dark'
229
+ : 'light',
230
+ },
231
+ });
232
+
233
+ const sadContext = createMockContext({
234
+ localHour: 10, // Daytime
235
+ emotionState: {
236
+ primary: 'sad',
237
+ valence: -0.6,
238
+ arousal: 0.3,
239
+ confidence: 0.8,
240
+ },
241
+ });
242
+
243
+ const decision = await customAdapter.route(
244
+ '/',
245
+ sadContext,
246
+ createMockTree(),
247
+ );
248
+
249
+ expect(decision.theme).toBe('dark');
250
+ });
251
+
252
+ test('uses custom predictNavigation', async () => {
253
+ const customAdapter = new HeuristicAdapter({
254
+ signals: {
255
+ predictNavigation: () => ['/custom1', '/custom2', '/custom3'],
256
+ },
257
+ });
258
+
259
+ const context = createMockContext();
260
+ const predictions = await customAdapter.speculate('/', context);
261
+
262
+ expect(predictions).toEqual(['/custom1', '/custom2', '/custom3']);
263
+ });
264
+ });
265
+
266
+ describe('tier-based feature flags', () => {
267
+ test('includes tier features in route decision', async () => {
268
+ const customAdapter = new HeuristicAdapter({
269
+ tierFeatures: {
270
+ free: { basicFeature: true, premiumFeature: false },
271
+ starter: { basicFeature: true, premiumFeature: true },
272
+ pro: { basicFeature: true, premiumFeature: true },
273
+ enterprise: { basicFeature: true, premiumFeature: true },
274
+ },
275
+ });
276
+
277
+ const freeContext = createMockContext({ tier: 'free' });
278
+ const starterContext = createMockContext({ tier: 'starter' });
279
+
280
+ const freeDecision = await customAdapter.route(
281
+ '/',
282
+ freeContext,
283
+ createMockTree(),
284
+ );
285
+ const starterDecision = await customAdapter.route(
286
+ '/',
287
+ starterContext,
288
+ createMockTree(),
289
+ );
290
+
291
+ expect(freeDecision.featureFlags?.premiumFeature).toBe(false);
292
+ expect(starterDecision.featureFlags?.premiumFeature).toBe(true);
293
+ });
294
+ });
295
+ });