@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.
- package/CHANGELOG.md +112 -0
- package/README.md +625 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +86 -0
- package/examples/basic/components/OfflineIndicator.tsx +103 -0
- package/examples/basic/components/PresenceBar.tsx +77 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +80 -0
- package/package.json +101 -0
- package/packages/analytics/README.md +309 -0
- package/packages/analytics/build.ts +35 -0
- package/packages/analytics/package.json +50 -0
- package/packages/analytics/src/click-tracker.ts +368 -0
- package/packages/analytics/src/context-bridge.ts +319 -0
- package/packages/analytics/src/data-layer.ts +302 -0
- package/packages/analytics/src/gtm-loader.ts +239 -0
- package/packages/analytics/src/index.ts +230 -0
- package/packages/analytics/src/merkle-tree.ts +489 -0
- package/packages/analytics/src/provider.tsx +300 -0
- package/packages/analytics/src/types.ts +320 -0
- package/packages/analytics/src/use-analytics.ts +296 -0
- package/packages/analytics/tsconfig.json +19 -0
- package/packages/benchmarks/src/benchmark.test.ts +691 -0
- package/packages/cli/dist/index.js +61899 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +682 -0
- package/packages/cli/src/commands/build.ts +890 -0
- package/packages/cli/src/commands/dev.ts +473 -0
- package/packages/cli/src/commands/init.ts +409 -0
- package/packages/cli/src/commands/start.ts +297 -0
- package/packages/cli/src/index.ts +105 -0
- package/packages/directives/src/use-aeon.ts +272 -0
- package/packages/mcp-server/package.json +51 -0
- package/packages/mcp-server/src/index.ts +178 -0
- package/packages/mcp-server/src/resources.ts +346 -0
- package/packages/mcp-server/src/tools/index.ts +36 -0
- package/packages/mcp-server/src/tools/navigation.ts +545 -0
- package/packages/mcp-server/tsconfig.json +21 -0
- package/packages/react/package.json +40 -0
- package/packages/react/src/Link.tsx +388 -0
- package/packages/react/src/components/InstallPrompt.tsx +286 -0
- package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
- package/packages/react/src/components/PushNotifications.tsx +453 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
- package/packages/react/src/hooks/useConflicts.ts +277 -0
- package/packages/react/src/hooks/useNetworkState.ts +209 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
- package/packages/react/src/hooks/useServiceWorker.ts +278 -0
- package/packages/react/src/hooks.ts +195 -0
- package/packages/react/src/index.ts +151 -0
- package/packages/react/src/provider.tsx +467 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/runtime/README.md +399 -0
- package/packages/runtime/build.ts +48 -0
- package/packages/runtime/package.json +71 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +465 -0
- package/packages/runtime/src/benchmark.ts +171 -0
- package/packages/runtime/src/cache.ts +479 -0
- package/packages/runtime/src/durable-object.ts +1341 -0
- package/packages/runtime/src/index.ts +360 -0
- package/packages/runtime/src/navigation.test.ts +421 -0
- package/packages/runtime/src/navigation.ts +422 -0
- package/packages/runtime/src/nextjs-adapter.ts +272 -0
- package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
- package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
- package/packages/runtime/src/offline/encryption.test.ts +412 -0
- package/packages/runtime/src/offline/encryption.ts +397 -0
- package/packages/runtime/src/offline/types.ts +465 -0
- package/packages/runtime/src/predictor.ts +371 -0
- package/packages/runtime/src/registry.ts +351 -0
- package/packages/runtime/src/router/context-extractor.ts +661 -0
- package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
- package/packages/runtime/src/router/esi-control.ts +541 -0
- package/packages/runtime/src/router/esi-cyrano.ts +779 -0
- package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
- package/packages/runtime/src/router/esi-react.tsx +1065 -0
- package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
- package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
- package/packages/runtime/src/router/esi-translate.ts +503 -0
- package/packages/runtime/src/router/esi.ts +666 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
- package/packages/runtime/src/router/index.ts +298 -0
- package/packages/runtime/src/router/merkle-capability.ts +473 -0
- package/packages/runtime/src/router/speculation.ts +451 -0
- package/packages/runtime/src/router/types.ts +630 -0
- package/packages/runtime/src/router.test.ts +470 -0
- package/packages/runtime/src/router.ts +302 -0
- package/packages/runtime/src/server.ts +481 -0
- package/packages/runtime/src/service-worker-push.ts +319 -0
- package/packages/runtime/src/service-worker.ts +553 -0
- package/packages/runtime/src/skeleton-hydrate.ts +237 -0
- package/packages/runtime/src/speculation.test.ts +389 -0
- package/packages/runtime/src/speculation.ts +486 -0
- package/packages/runtime/src/storage.test.ts +1297 -0
- package/packages/runtime/src/storage.ts +1048 -0
- package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
- package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
- package/packages/runtime/src/sync/coordinator.test.ts +608 -0
- package/packages/runtime/src/sync/coordinator.ts +596 -0
- package/packages/runtime/src/tree-compiler.ts +295 -0
- package/packages/runtime/src/types.ts +728 -0
- package/packages/runtime/src/worker.ts +327 -0
- package/packages/runtime/tsconfig.json +20 -0
- package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
- package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
- package/packages/runtime/wasm/package.json +21 -0
- package/packages/runtime/wrangler.toml +41 -0
- package/packages/runtime-wasm/Cargo.lock +436 -0
- package/packages/runtime-wasm/Cargo.toml +29 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
- package/packages/runtime-wasm/pkg/package.json +21 -0
- package/packages/runtime-wasm/src/hydrate.rs +352 -0
- package/packages/runtime-wasm/src/lib.rs +191 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/packages/runtime-wasm/src/skeleton.rs +430 -0
- 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
|
+
});
|