@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
package/README.md
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# @affectively/aeon-flux
|
|
2
|
+
|
|
3
|
+
**The CMS IS the website.** Collaborative page framework with CRDT-based flux state and hyperpersonalized routing.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
'use aeon'; // One directive enables everything
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## What is Aeon Flux?
|
|
10
|
+
|
|
11
|
+
Aeon Flux is a lightweight, collaborative page framework that unifies:
|
|
12
|
+
|
|
13
|
+
- **CMS = Website** - No separate admin panel, edit in place
|
|
14
|
+
- **Editor = Viewer** - Click to edit, changes are live
|
|
15
|
+
- **Backend = Frontend** - One runtime, runs on edge
|
|
16
|
+
- **Pages from D1** - No file system, pure database
|
|
17
|
+
- **AI Everywhere** - Edge Side Inference (ESI) brings AI to any component
|
|
18
|
+
|
|
19
|
+
The name comes from:
|
|
20
|
+
1. **Flux** - CRDT-based data model for conflict-free collaboration
|
|
21
|
+
2. **Aeon Flux** - The classic anime (a syzygy of human and machine)
|
|
22
|
+
|
|
23
|
+
## Key Features
|
|
24
|
+
|
|
25
|
+
| Feature | Description |
|
|
26
|
+
|---------|-------------|
|
|
27
|
+
| **Zero-Dependency Rendering** | Single HTML with inline CSS, assets, fonts |
|
|
28
|
+
| **Hyperpersonalized Routing** | The website comes to the person |
|
|
29
|
+
| **Edge Side Inference (ESI)** | AI inference at render time |
|
|
30
|
+
| **Pages from D1** | No file system in production |
|
|
31
|
+
| **~20KB WASM runtime** | vs 500KB+ Next.js |
|
|
32
|
+
| **Multi-layer caching** | KV (1ms) → D1 (5ms) → Session (50ms) |
|
|
33
|
+
| **Speculative pre-rendering** | Zero-latency navigation |
|
|
34
|
+
| **Built-in collaboration** | Real-time editing with presence |
|
|
35
|
+
| **GitHub PR Publishing** | Visual edits → TSX → Git → Deploy |
|
|
36
|
+
| **Lazy hydration** | Only hydrate interactive components on visibility |
|
|
37
|
+
|
|
38
|
+
## Live Infrastructure
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
https://aeon-flux.taylorbuley.workers.dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Endpoint | Description |
|
|
45
|
+
|----------|-------------|
|
|
46
|
+
| `/health` | Health check |
|
|
47
|
+
| `/session/:id` | WebSocket for real-time collab |
|
|
48
|
+
| `/session/:id/session` | Session state (GET/PUT) |
|
|
49
|
+
| `/session/:id/presence` | Active users |
|
|
50
|
+
| `/routes` | Route registry |
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Install
|
|
56
|
+
bun add @affectively/aeon-pages-runtime
|
|
57
|
+
|
|
58
|
+
# Or use the worker directly
|
|
59
|
+
curl https://aeon-flux.taylorbuley.workers.dev/health
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Hyperpersonalized Routing
|
|
63
|
+
|
|
64
|
+
**"The website comes to the person"** - Routes adapt based on user context.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { HeuristicAdapter, extractUserContext } from '@affectively/aeon-pages-runtime';
|
|
68
|
+
|
|
69
|
+
const adapter = new HeuristicAdapter({
|
|
70
|
+
defaultPaths: ['/', '/chat', '/settings'],
|
|
71
|
+
signals: {
|
|
72
|
+
deriveTheme: (ctx) => ctx.localHour > 18 ? 'dark' : 'light',
|
|
73
|
+
deriveAccent: (ctx) => ctx.emotionState?.primary === 'happy' ? '#FFD700' : '#6366f1',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Route decision includes personalization
|
|
78
|
+
const decision = await adapter.route('/dashboard', userContext, componentTree);
|
|
79
|
+
// { theme: 'dark', accent: '#FFD700', prefetch: ['/chat'], density: 'comfortable', ... }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Performance
|
|
83
|
+
|
|
84
|
+
| Operation | 100 nodes | 500 nodes |
|
|
85
|
+
|-----------|-----------|-----------|
|
|
86
|
+
| `route()` | 0.014ms | 0.05ms |
|
|
87
|
+
| `speculate()` | 0.003ms | 0.007ms |
|
|
88
|
+
| `personalizeTree()` | 0.026ms | 0.109ms |
|
|
89
|
+
|
|
90
|
+
Sub-millisecond routing on every request.
|
|
91
|
+
|
|
92
|
+
## Edge Side Inference (ESI)
|
|
93
|
+
|
|
94
|
+
Bring AI to any component at render time. Like Varnish ESI, but for inference.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { ESI, ESIProvider } from '@affectively/aeon-pages-runtime/router';
|
|
98
|
+
|
|
99
|
+
// Basic inference
|
|
100
|
+
<ESI.Infer model="llm" cache={300}>
|
|
101
|
+
Summarize this page for {user.name}
|
|
102
|
+
</ESI.Infer>
|
|
103
|
+
|
|
104
|
+
// Structured output with Zod
|
|
105
|
+
<ESI.Structured schema={z.object({ sentiment: z.enum(['positive', 'negative']) })}>
|
|
106
|
+
Analyze: {userMessage}
|
|
107
|
+
</ESI.Structured>
|
|
108
|
+
|
|
109
|
+
// Conditional rendering
|
|
110
|
+
<ESI.If
|
|
111
|
+
prompt="Should we show a discount?"
|
|
112
|
+
schema={z.object({ show: z.boolean() })}
|
|
113
|
+
when={(r) => r.show}
|
|
114
|
+
>
|
|
115
|
+
<DiscountBanner />
|
|
116
|
+
</ESI.If>
|
|
117
|
+
|
|
118
|
+
// Presence-aware (adapts for multiple viewers)
|
|
119
|
+
<ESI.Collaborative schema={summarySchema}>
|
|
120
|
+
Summarize for {presence.length} viewers: {content}
|
|
121
|
+
</ESI.Collaborative>
|
|
122
|
+
|
|
123
|
+
// Self-optimizing (improves when user is alone)
|
|
124
|
+
<ESI.Optimize
|
|
125
|
+
schema={contentSchema}
|
|
126
|
+
maxIterations={3}
|
|
127
|
+
goal="Improve clarity and engagement"
|
|
128
|
+
>
|
|
129
|
+
{draftContent}
|
|
130
|
+
</ESI.Optimize>
|
|
131
|
+
|
|
132
|
+
// Format transformations - wrap any ESI output
|
|
133
|
+
<ESI.Markdown gfm>
|
|
134
|
+
<ESI.Infer>Generate API documentation</ESI.Infer>
|
|
135
|
+
</ESI.Markdown>
|
|
136
|
+
|
|
137
|
+
<ESI.Latex displayMode>
|
|
138
|
+
<ESI.Infer>Write the quadratic formula</ESI.Infer>
|
|
139
|
+
</ESI.Latex>
|
|
140
|
+
|
|
141
|
+
<ESI.Json indent={4} copyable>
|
|
142
|
+
<ESI.Structured schema={dataSchema}>Analyze this</ESI.Structured>
|
|
143
|
+
</ESI.Json>
|
|
144
|
+
|
|
145
|
+
// Text-to-code with coding models
|
|
146
|
+
<ESI.Code
|
|
147
|
+
generateFrom="A debounce utility function"
|
|
148
|
+
language="typescript"
|
|
149
|
+
model="codestral"
|
|
150
|
+
lineNumbers
|
|
151
|
+
copyable
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### ESI Format Components
|
|
156
|
+
|
|
157
|
+
Transform inference output before rendering:
|
|
158
|
+
|
|
159
|
+
| Component | Purpose |
|
|
160
|
+
|-----------|---------|
|
|
161
|
+
| `ESI.Markdown` | Render markdown as HTML (GFM support) |
|
|
162
|
+
| `ESI.Latex` | Render LaTeX math expressions |
|
|
163
|
+
| `ESI.Json` | Pretty-print JSON with syntax highlighting |
|
|
164
|
+
| `ESI.Plaintext` | Plain text with whitespace control |
|
|
165
|
+
| `ESI.Code` | Code blocks with text-to-code generation |
|
|
166
|
+
| `ESI.Semantic` | Extract topics/entities → structured HTML/microdata |
|
|
167
|
+
|
|
168
|
+
### ESI.Code with Coding Models
|
|
169
|
+
|
|
170
|
+
`ESI.Code` supports specialized coding models for text-to-code generation:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// Text-to-code: generate from natural language
|
|
174
|
+
<ESI.Code
|
|
175
|
+
generateFrom="A React hook that fetches user data with loading state"
|
|
176
|
+
language="typescript"
|
|
177
|
+
model="codestral"
|
|
178
|
+
lineNumbers
|
|
179
|
+
copyable
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
// Available models
|
|
183
|
+
type CodeModel =
|
|
184
|
+
| 'codestral' // Mistral Codestral (default)
|
|
185
|
+
| 'deepseek' // DeepSeek Coder
|
|
186
|
+
| 'starcoder' // StarCoder
|
|
187
|
+
| 'codellama' // Code Llama
|
|
188
|
+
| 'qwen-coder' // Qwen Coder
|
|
189
|
+
| 'claude' // Claude
|
|
190
|
+
| 'gpt-4'; // GPT-4
|
|
191
|
+
|
|
192
|
+
// Auto-detect language
|
|
193
|
+
<ESI.Code autoDetect model="deepseek">
|
|
194
|
+
{someCodeString}
|
|
195
|
+
</ESI.Code>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### ESI.Semantic - Embeddings to Structured HTML
|
|
199
|
+
|
|
200
|
+
Extract semantic topics, entities, and emotions → generate Schema.org microdata:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
// Extract topics as JSON-LD structured data
|
|
204
|
+
<ESI.Semantic format="jsonld" schemaType="Article" extractEntities extractEmotion>
|
|
205
|
+
{articleText}
|
|
206
|
+
</ESI.Semantic>
|
|
207
|
+
|
|
208
|
+
// Display as interactive tags
|
|
209
|
+
<ESI.Semantic format="tags" maxTopics={5} extractEmotion>
|
|
210
|
+
<ESI.Infer>Summarize this news article</ESI.Infer>
|
|
211
|
+
</ESI.Semantic>
|
|
212
|
+
|
|
213
|
+
// Output formats: microdata | jsonld | rdfa | tags
|
|
214
|
+
// Uses: embed model (embeddings), classify model (entities), emotion model
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Zero-Dependency Rendering
|
|
218
|
+
|
|
219
|
+
Every page is a **completely self-contained HTML document**. No external requests needed.
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
Single Request → Instant Render
|
|
223
|
+
├── Inline CSS (tree-shaken, only used classes)
|
|
224
|
+
├── Inline assets (SVG, images as data URIs)
|
|
225
|
+
├── Inline fonts (@font-face with embedded data)
|
|
226
|
+
├── Minimal hydration script (lazy, on visibility)
|
|
227
|
+
└── WASM-rendered at edge (~7ms total)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Multi-Layer Caching
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
Request → KV Cache (1ms) → D1 Cache (5ms) → Session Render (50ms)
|
|
234
|
+
↓ ↓ ↓
|
|
235
|
+
Return HTML Cache to KV Cache to KV + D1
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
All pages are pre-rendered at build time and cached. First request for any route is a cache hit.
|
|
239
|
+
|
|
240
|
+
### Speculative Pre-Rendering
|
|
241
|
+
|
|
242
|
+
Pages are pre-rendered **before the user clicks**, based on:
|
|
243
|
+
- Link visibility (IntersectionObserver)
|
|
244
|
+
- Hover intent
|
|
245
|
+
- Navigation prediction (Markov chain, community patterns)
|
|
246
|
+
- Browser Speculation Rules API
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { initSpeculativeRendering } from '@affectively/aeon-pages-runtime';
|
|
250
|
+
|
|
251
|
+
// Enable instant navigation
|
|
252
|
+
initSpeculativeRendering({
|
|
253
|
+
maxCachedPages: 5,
|
|
254
|
+
prerenderOnHover: true,
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Performance
|
|
259
|
+
|
|
260
|
+
| Metric | Before | After |
|
|
261
|
+
|--------|--------|-------|
|
|
262
|
+
| Requests | 15-30 | 1 |
|
|
263
|
+
| Total bytes | ~655KB | ~110KB |
|
|
264
|
+
| TTFB | 100ms | 50ms |
|
|
265
|
+
| First Paint | 500ms | <100ms |
|
|
266
|
+
| Time to Interactive | 2000ms | <300ms |
|
|
267
|
+
| CLS | 0.05 | 0 |
|
|
268
|
+
|
|
269
|
+
## GitHub PR Publishing
|
|
270
|
+
|
|
271
|
+
Visual edits compile to TSX and create PRs automatically.
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
Edit in browser → Durable Object → "Publish" → TSX → PR → CI deploys
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### WebSocket Commands
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
const ws = new WebSocket('wss://aeon-flux.taylorbuley.workers.dev/session/my-page?userId=user1');
|
|
281
|
+
|
|
282
|
+
// Publish current tree → creates PR
|
|
283
|
+
ws.send(JSON.stringify({ type: 'publish' }));
|
|
284
|
+
// Response: { type: 'publish', payload: { prNumber: 123, autoMerged: false } }
|
|
285
|
+
|
|
286
|
+
// Merge a PR
|
|
287
|
+
ws.send(JSON.stringify({ type: 'merge', payload: { prNumber: 123 } }));
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Generated TSX
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
'use aeon';
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* AboutPage
|
|
297
|
+
* Route: /about
|
|
298
|
+
*
|
|
299
|
+
* @generated by aeon-flux visual editor
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
import type { FC } from 'react';
|
|
303
|
+
import { Hero } from '@/components/Hero';
|
|
304
|
+
import { Section } from '@/components/Section';
|
|
305
|
+
|
|
306
|
+
const AboutPage: FC = () => {
|
|
307
|
+
return (
|
|
308
|
+
<Page>
|
|
309
|
+
<Hero title="About Us" />
|
|
310
|
+
<Section>
|
|
311
|
+
<Text>We believe in...</Text>
|
|
312
|
+
</Section>
|
|
313
|
+
</Page>
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export default AboutPage;
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Configuration
|
|
321
|
+
|
|
322
|
+
```toml
|
|
323
|
+
# wrangler.toml
|
|
324
|
+
[vars]
|
|
325
|
+
GITHUB_REPO = "owner/repo"
|
|
326
|
+
GITHUB_TREE_PATH = "apps/web/pages"
|
|
327
|
+
GITHUB_BASE_BRANCH = "staging"
|
|
328
|
+
GITHUB_DEV_BRANCH = "development"
|
|
329
|
+
GITHUB_AUTO_MERGE = "false"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Production Architecture
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
336
|
+
│ AEON FLUX ARCHITECTURE │
|
|
337
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
338
|
+
│ │
|
|
339
|
+
│ Request + User Context │
|
|
340
|
+
│ │ │
|
|
341
|
+
│ ▼ │
|
|
342
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
343
|
+
│ │ HeuristicAdapter │ │ ESI │ │
|
|
344
|
+
│ │ (personalize) │ │ (inference) │ │
|
|
345
|
+
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
346
|
+
│ │ │ │
|
|
347
|
+
│ ▼ ▼ │
|
|
348
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
349
|
+
│ │ Durable Objects │ │
|
|
350
|
+
│ │ (sessions, presence, real-time sync) │ │
|
|
351
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
352
|
+
│ │ │ │
|
|
353
|
+
│ ▼ ▼ │
|
|
354
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
355
|
+
│ │ D1 │ │ GitHub │ │
|
|
356
|
+
│ │ (storage) │ │ (publish) │ │
|
|
357
|
+
│ └──────────────┘ └──────────────┘ │
|
|
358
|
+
│ │
|
|
359
|
+
│ Edit visually → Sync via DO → Publish → TSX → PR → Deploy │
|
|
360
|
+
│ │
|
|
361
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Local-First with Dash
|
|
365
|
+
|
|
366
|
+
Data lives client-side. No backend services needed.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { DashStorageAdapter } from '@affectively/aeon-pages-runtime';
|
|
370
|
+
|
|
371
|
+
const storage = new DashStorageAdapter(dashClient, {
|
|
372
|
+
routesCollection: 'aeon-routes',
|
|
373
|
+
sessionsCollection: 'aeon-sessions',
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Reads: instant (local)
|
|
377
|
+
// Writes: instant (local), syncs via DO
|
|
378
|
+
// Collab: Durable Objects
|
|
379
|
+
// AI: ESI at edge
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Speculation & Prefetching
|
|
383
|
+
|
|
384
|
+
Predict and prefetch likely next pages.
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { SpeculationManager, createSpeculationHook } from '@affectively/aeon-pages-runtime/router';
|
|
388
|
+
|
|
389
|
+
// Client-side
|
|
390
|
+
const manager = new SpeculationManager({
|
|
391
|
+
maxPrefetch: 5,
|
|
392
|
+
hoverDelay: 100,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Prefetch on hover
|
|
396
|
+
manager.observeLinks();
|
|
397
|
+
|
|
398
|
+
// Or use the hook
|
|
399
|
+
const useSpeculation = createSpeculationHook(manager);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Supports [Speculation Rules API](https://developer.chrome.com/docs/web-platform/prerender-pages) with link prefetch fallback.
|
|
403
|
+
|
|
404
|
+
## Zero-Instrumentation Analytics
|
|
405
|
+
|
|
406
|
+
Automatic click tracking with Merkle tree node identification. Every click tracked without writing a single line of instrumentation code.
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import {
|
|
410
|
+
initClickTracker,
|
|
411
|
+
initContextBridgeWithRetry,
|
|
412
|
+
pushPageView,
|
|
413
|
+
} from '@affectively/aeon-pages-analytics';
|
|
414
|
+
|
|
415
|
+
// Initialize on client
|
|
416
|
+
initContextBridgeWithRetry({ maxRetries: 3, retryDelayMs: 500 });
|
|
417
|
+
initClickTracker({
|
|
418
|
+
debounceMs: 100,
|
|
419
|
+
maxTextLength: 150,
|
|
420
|
+
excludeSelectors: ['.no-track'],
|
|
421
|
+
});
|
|
422
|
+
pushPageView();
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### What Gets Tracked
|
|
426
|
+
|
|
427
|
+
Every click automatically includes:
|
|
428
|
+
|
|
429
|
+
| Data | Description |
|
|
430
|
+
|------|-------------|
|
|
431
|
+
| **Merkle Hash** | Content-addressable ID (stable across renders) |
|
|
432
|
+
| **Tree Path** | `['root', 'layout', 'header', 'nav', 'button']` |
|
|
433
|
+
| **ESI Context** | User tier, emotion state, features, session |
|
|
434
|
+
| **Element Info** | Tag, text, aria-label, role, href |
|
|
435
|
+
| **Position** | Viewport and document coordinates |
|
|
436
|
+
|
|
437
|
+
### DataLayer Events
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
// Click event pushed to dataLayer
|
|
441
|
+
{
|
|
442
|
+
event: 'aeon.click',
|
|
443
|
+
click: {
|
|
444
|
+
merkleHash: 'a1b2c3d4e5f6',
|
|
445
|
+
treePath: ['root', 'layout', 'header', 'settings-btn'],
|
|
446
|
+
element: { tagName: 'BUTTON', text: 'Settings' }
|
|
447
|
+
},
|
|
448
|
+
context: {
|
|
449
|
+
userTier: 'pro',
|
|
450
|
+
emotionState: { primary: 'focused', valence: 0.3 }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Works with GTM + GA4 out of the box. Set `GTM_CONTAINER_ID` in your environment.
|
|
456
|
+
|
|
457
|
+
## The `'use aeon'` Directive
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
'use aeon';
|
|
461
|
+
|
|
462
|
+
export default function Page() {
|
|
463
|
+
const {
|
|
464
|
+
presence, // Who's viewing/editing
|
|
465
|
+
sync, // Sync state and controls
|
|
466
|
+
version, // Schema version info
|
|
467
|
+
data, // Collaborative data store
|
|
468
|
+
setData, // Update data
|
|
469
|
+
} = useAeonPage();
|
|
470
|
+
|
|
471
|
+
return <div>...</div>;
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Hooks
|
|
476
|
+
|
|
477
|
+
| Hook | Purpose |
|
|
478
|
+
|------|---------|
|
|
479
|
+
| `useAeonPage()` | Full page context |
|
|
480
|
+
| `usePresence()` | Who's here, cursors, editing |
|
|
481
|
+
| `useAeonData<T>(key)` | Collaborative typed data |
|
|
482
|
+
| `useCollaborativeInput(key)` | Ready-to-use collab input |
|
|
483
|
+
| `useOfflineStatus()` | Offline awareness |
|
|
484
|
+
| `useESI()` | ESI context |
|
|
485
|
+
| `useESIInfer()` | Programmatic inference |
|
|
486
|
+
| `useESITier()` | User tier for feature gating |
|
|
487
|
+
| `useESIEmotionState()` | Current emotional context |
|
|
488
|
+
| `useESIFeature(name)` | Check feature availability |
|
|
489
|
+
| `useIsAdmin()` | Check if user is admin |
|
|
490
|
+
| `useMeetsTierRequirement(tier)` | Check if user meets tier |
|
|
491
|
+
| `useGlobalESIState()` | Full ESI state object |
|
|
492
|
+
|
|
493
|
+
## ESI Global State Injection
|
|
494
|
+
|
|
495
|
+
For zero-CLS tier-aware rendering, inject ESI state in the `<head>`:
|
|
496
|
+
|
|
497
|
+
```html
|
|
498
|
+
<script>
|
|
499
|
+
window.__AEON_ESI_STATE__ = {
|
|
500
|
+
userTier: 'pro', // free | starter | pro | enterprise
|
|
501
|
+
emotionState: {
|
|
502
|
+
primary: 'focused',
|
|
503
|
+
valence: 0.3,
|
|
504
|
+
arousal: 0.6
|
|
505
|
+
},
|
|
506
|
+
preferences: {
|
|
507
|
+
theme: 'dark',
|
|
508
|
+
reducedMotion: false
|
|
509
|
+
},
|
|
510
|
+
sessionId: 'abc123',
|
|
511
|
+
localHour: 14,
|
|
512
|
+
timezone: 'America/New_York'
|
|
513
|
+
};
|
|
514
|
+
</script>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Then use in components:
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
import { useESITier } from '@affectively/aeon-flux/esi';
|
|
521
|
+
|
|
522
|
+
function PremiumFeature() {
|
|
523
|
+
const tier = useESITier();
|
|
524
|
+
|
|
525
|
+
if (tier === 'free') {
|
|
526
|
+
return <UpgradePrompt />;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return <AdvancedAnalytics />;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
## Configuration
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// aeon.config.ts
|
|
536
|
+
export default {
|
|
537
|
+
pagesDir: './pages',
|
|
538
|
+
runtime: 'cloudflare',
|
|
539
|
+
|
|
540
|
+
router: {
|
|
541
|
+
adapter: 'heuristic',
|
|
542
|
+
speculation: {
|
|
543
|
+
enabled: true,
|
|
544
|
+
depth: 2,
|
|
545
|
+
prerenderTop: 1,
|
|
546
|
+
},
|
|
547
|
+
personalization: {
|
|
548
|
+
featureGating: true,
|
|
549
|
+
emotionTheming: true,
|
|
550
|
+
componentOrdering: true,
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
esi: {
|
|
555
|
+
enabled: true,
|
|
556
|
+
endpoint: process.env.ESI_ENDPOINT,
|
|
557
|
+
timeout: 5000,
|
|
558
|
+
defaultCacheTtl: 300,
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
github: {
|
|
562
|
+
repo: 'owner/repo',
|
|
563
|
+
treePath: 'apps/web/pages',
|
|
564
|
+
baseBranch: 'staging',
|
|
565
|
+
devBranch: 'development',
|
|
566
|
+
autoMerge: false,
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## Packages
|
|
572
|
+
|
|
573
|
+
| Package | Description |
|
|
574
|
+
|---------|-------------|
|
|
575
|
+
| `@affectively/aeon-flux` | Main package (npm) |
|
|
576
|
+
| `@affectively/aeon-flux/esi` | ESI hooks for tier gating |
|
|
577
|
+
| `@affectively/aeon-flux/react` | React bindings |
|
|
578
|
+
| `@affectively/aeon-flux/server` | Server utilities |
|
|
579
|
+
| `@affectively/aeon-pages-runtime` | Runtime (npm) |
|
|
580
|
+
| `@affectively/aeon-pages-runtime/router` | Personalized routing + ESI |
|
|
581
|
+
| `@affectively/aeon-pages-runtime/server` | Server utilities |
|
|
582
|
+
| `@affectively/aeon-pages-analytics` | Zero-instrumentation analytics with Merkle tree tracking |
|
|
583
|
+
|
|
584
|
+
## Deploy Your Own
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
cd packages/runtime
|
|
588
|
+
|
|
589
|
+
# Deploy worker
|
|
590
|
+
wrangler deploy
|
|
591
|
+
|
|
592
|
+
# Set GitHub token
|
|
593
|
+
wrangler secret put GITHUB_TOKEN
|
|
594
|
+
|
|
595
|
+
# Create D1 database (optional)
|
|
596
|
+
wrangler d1 create aeon-flux
|
|
597
|
+
wrangler d1 execute aeon-flux --file=./schema.sql
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## vs Next.js
|
|
601
|
+
|
|
602
|
+
| | Next.js | Aeon Flux |
|
|
603
|
+
|-|---------|-----------|
|
|
604
|
+
| **Runtime** | ~500KB | ~187KB |
|
|
605
|
+
| **Routing** | Static | Personalized |
|
|
606
|
+
| **AI** | Add-on | ESI built-in |
|
|
607
|
+
| **CMS** | Separate | It IS the site |
|
|
608
|
+
| **Collaboration** | Add-on | Built-in |
|
|
609
|
+
| **Publish** | Manual | Visual → PR |
|
|
610
|
+
| **Edge** | Partial | Full |
|
|
611
|
+
|
|
612
|
+
## Requirements
|
|
613
|
+
|
|
614
|
+
- Bun >= 1.0.0
|
|
615
|
+
- React >= 18.0.0
|
|
616
|
+
- Zod >= 3.0.0
|
|
617
|
+
- Cloudflare account (for production)
|
|
618
|
+
|
|
619
|
+
## License
|
|
620
|
+
|
|
621
|
+
MIT
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
*The CMS is the website. The website comes to the person. Visual edits become code.*
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AeonConfig } from '@affectively/aeon-pages';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
pagesDir: './pages',
|
|
5
|
+
componentsDir: './components',
|
|
6
|
+
runtime: 'bun',
|
|
7
|
+
port: 3000,
|
|
8
|
+
|
|
9
|
+
aeon: {
|
|
10
|
+
sync: {
|
|
11
|
+
mode: 'distributed',
|
|
12
|
+
replicationFactor: 3,
|
|
13
|
+
consistencyLevel: 'strong',
|
|
14
|
+
},
|
|
15
|
+
versioning: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
autoMigrate: true,
|
|
18
|
+
},
|
|
19
|
+
presence: {
|
|
20
|
+
enabled: true,
|
|
21
|
+
cursorTracking: true,
|
|
22
|
+
inactivityTimeout: 60000,
|
|
23
|
+
},
|
|
24
|
+
offline: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
maxQueueSize: 1000,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
components: {
|
|
31
|
+
autoDiscover: true,
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
nextCompat: true,
|
|
35
|
+
|
|
36
|
+
output: {
|
|
37
|
+
dir: '.aeon',
|
|
38
|
+
},
|
|
39
|
+
} satisfies AeonConfig;
|