{"_id":"@affectively/edge-web-app","_rev":"2-da529e70eae9e505b96ea2d640660c82","name":"@affectively/edge-web-app","dist-tags":{"latest":"5.0.0"},"versions":{"5.0.0":{"name":"@affectively/edge-web-app","version":"5.0.0","_id":"@affectively/edge-web-app@5.0.0","maintainers":[{"name":"buley","email":"buley@outlook.com"}],"dist":{"shasum":"49ba70a4d5518a8f75ef3847848c2378d14246b1","tarball":"https://registry.npmjs.org/@affectively/edge-web-app/-/edge-web-app-5.0.0.tgz","fileCount":1496,"integrity":"sha512-8hr9yhjagC7NuoM0Bss7GMZvhRPRP7QGpyFNPvgt2tqYueEGrtWzUFeaELMpClBnncDSfmAr27teJFxn3iN+FQ==","signatures":[{"sig":"MEQCIDZc6apM7jJS95NKvIRlK6hGRxDe/rNX8Yov2o2k9p14AiA2fgHr8e9XM306Nj60GxdHlC8mXkC8L1VkG4J5BJsrMg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15648416},"type":"module","engines":{"bun":">=1.0.0"},"gitHead":"ae54b0df6ac98ed86b037accafa16cf218dda541","scripts":{"dev":"wrangler dev","sync":"bun scripts/sync-pages.ts pull","build":"bun run prerender && bun run build:css && bun run build:worker && bun run build:client","deploy":"bun run build && wrangler deploy --env production && bun run bootstrap:production && bun run register:edgework:production && bun run register:aeonpid","db:seed":"bun scripts/seed-d1.ts","preview":"wrangler dev --local","db:setup":"bun run db:migrate && bun run db:seed","dev:sync":"bun scripts/sync-pages.ts watch","bootstrap":"bun scripts/bootstrap-pages.ts","build:css":"bunx tailwindcss -i ./src/styles/globals.css -o ./public/styles.css --minify","db:create":"wrangler d1 create edge-web-app-db","prerender":"bun scripts/prerender-pages.ts","sync:push":"bun scripts/sync-pages.ts push","typecheck":"bun tsc -p tsconfig.typecheck.json --noEmit","db:migrate":"wrangler d1 execute edge-web-app-db --file=migrations/0001_initial.sql","deploy:all":"bun run build && wrangler deploy --env dev && bun run register:edgework:dev && bun run register:aeonpid:dev && wrangler deploy --env staging && bun run register:edgework:staging && bun run register:aeonpid:staging && wrangler deploy --env production && bun run register:edgework:production && bun run register:aeonpid:production","deploy:dev":"bun run build && wrangler deploy --env dev && bun run register:edgework:dev && bun run register:aeonpid:dev","db:seed:dry":"bun scripts/seed-d1.ts --dry-run","build:client":"bun scripts/build-client.ts","build:worker":"bun scripts/generate-build-hash.ts && bun build ./src/worker.ts --outdir .aeon/dist --target bun --minify","db:seed:bulk":"bash ./scripts/seed-all-pages.sh","forge:deploy":"bun ../../apps/aeon-forge/src/cli/cli.tsx deploy . --oneshot","bootstrap:all":"bun run bootstrap && bun run bootstrap:learning && bun run bootstrap:blog","db:seed:local":"bun scripts/seed-d1.ts --local","bootstrap:blog":"bun scripts/bootstrap-blog.ts","db:setup:local":"bun run db:migrate:local && bun run db:seed:local","deploy:staging":"bun run build && wrangler deploy --env staging && bun run bootstrap:staging && bun run register:edgework:staging && bun run register:aeonpid:staging","db:migrate:local":"wrangler d1 execute edge-web-app-db --local --file=migrations/0001_initial.sql","register:aeonpid":"bun ../../scripts/register-aeonpid.ts edge-web-app production","bootstrap:staging":"AEON_BACKEND_URL=https://aeon-flux.taylorbuley.workers.dev bun run bootstrap:all","deploy:production":"bun run build && wrangler deploy --env production && bun run bootstrap:production && bun run db:seed && bun run register:edgework:production && bun run register:aeonpid:production","register:edgework":"bun scripts/register-edgework.ts production","bootstrap:learning":"bun scripts/bootstrap-learning.ts","bootstrap:production":"AEON_BACKEND_URL=https://aeon-flux.taylorbuley.workers.dev bun run bootstrap:all","register:aeonpid:dev":"bun ../../scripts/register-aeonpid.ts edge-web-app dev","register:edgework:dev":"bun scripts/register-edgework.ts dev","register:aeonpid:staging":"bun ../../scripts/register-aeonpid.ts edge-web-app staging","register:edgework:staging":"bun scripts/register-edgework.ts staging","register:aeonpid:production":"bun ../../scripts/register-aeonpid.ts edge-web-app production","register:edgework:production":"bun scripts/register-edgework.ts production"},"_npmUser":{"name":"buley","email":"buley@outlook.com"},"_npmVersion":"11.8.0","description":"AFFECTIVELY Edge Web App - Aeon Flux powered collaborative experience","directories":{},"_nodeVersion":"25.5.0","dependencies":{"ai":"^6.0.0","react":"19.2.3","three":"^0.182.0","react-dom":"19.2.3","@ai-sdk/openai":"^3.0.26","@affectively/aeon":"workspace:*","@affectively/dash":"workspace:*","@buley/hexgrid-3d":"^3.6.4","@react-three/drei":"^10.7.7","@react-three/fiber":"^9.4.2","workers-ai-provider":"^3.0.0","@affectively/aeon-flux":"workspace:*","@affectively/aeon-build":"workspace:*","@affectively/aeon-flags":"^1.0.1","@affectively/aeon-icons":"workspace:*","@affectively/aeon-invite":"workspace:*","@affectively/edgework-sdk":"workspace:*","@affectively/shared-utils":"workspace:*","@affectively/slash-commands":"^1.1.0","@affectively/aeon-flux-react":"workspace:*","@affectively/aeon-flux-runtime":"workspace:*","@affectively/aeon-flux-analytics":"workspace:*"},"_hasShrinkwrap":false,"devDependencies":{"postcss":"^8.5.1","wrangler":"^4.14.0","typescript":"^5.7.0","@types/node":"^22.0.0","tailwindcss":"^3.4.17","@types/react":"19.2.14","autoprefixer":"^10.4.21","@types/react-dom":"19.2.3","@cloudflare/workers-types":"^4.20240725.0"},"_npmOperationalInternal":{"tmp":"tmp/edge-web-app_5.0.0_1773155928906_0.6649381334624898","host":"s3://npm-registry-packages-npm-production"},"deprecated":"This package is deprecated and no longer supported. Do not use."}},"time":{"created":"2026-03-10T15:18:48.806Z","modified":"2026-03-27T20:03:31.977Z","5.0.0":"2026-03-10T15:18:49.317Z"},"description":"AFFECTIVELY Edge Web App - Aeon Flux powered collaborative experience","maintainers":[{"name":"buley","email":"buley@outlook.com"}],"readme":"# Edge Web App\n\nCloudflare Worker serving the AFFECTIVELY web application. Pages rendered via Aeon-Flux, API routes proxied to api-edge service.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                     edge-web-app                            │\n│                   (Cloudflare Worker)                       │\n├─────────────────────────────────────────────────────────────┤\n│                                                             │\n│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │\n│  │   /api/*    │    │   Pages     │    │   /health   │     │\n│  │   Proxy     │    │  Rendering  │    │   Check     │     │\n│  └──────┬──────┘    └──────┬──────┘    └─────────────┘     │\n│         │                  │                                │\n│         ▼                  ▼                                │\n│  ┌─────────────┐    ┌─────────────┐                        │\n│  │  api-edge   │    │  KV Cache   │                        │\n│  │  (service)  │    │  (CACHE)    │                        │\n│  └─────────────┘    └──────┬──────┘                        │\n│                            │                                │\n│                            ▼                                │\n│                     ┌─────────────┐                        │\n│                     │  Aeon-Flux  │                        │\n│                     │  (backend)  │                        │\n│                     └─────────────┘                        │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n```\n\n## Features\n\n- **Page Rendering**: Client-hydrated pages with server-side shell generation\n- **Device-Aware SSR**: Mobile/tablet/desktop optimizations at the edge\n- **KV Page Cache**: UCAN-aware caching with per-user and anonymous strategies\n- **API Proxy**: Routes `/api/*` to api-edge service via service binding\n- **Delegated Admin Verification**: Fail-closed admin and superadmin checks via Fractal IAM for privileged routes\n- **Static Assets**: Served from `/public` directory\n- **CORS Support**: Full CORS headers for cross-origin requests\n\n## Device-Aware Edge Optimization\n\nThe edge worker detects device type and delivers optimized HTML/CSS per device class.\n\n### How It Works\n\n```\nRequest → Cloudflare Edge\n    │\n    ├─ Device Detection (CF-Device-Type header or User-Agent)\n    │     └─ mobile | tablet | desktop\n    │\n    ├─ CSS Sharding\n    │     └─ Mobile: base + sm: classes (~50% smaller)\n    │     └─ Desktop: all responsive variants\n    │\n    ├─ DOM Pruning\n    │     └─ Mobile: removes \"hidden md:block\" elements\n    │     └─ Desktop: removes \"md:hidden\" elements\n    │\n    └─ Inline Styles\n          └─ 40+ common Tailwind classes → style attribute\n          └─ Zero CSS selector matching for critical path\n```\n\n### Performance Savings\n\n| Optimization | Desktop | Mobile | Savings |\n|--------------|---------|--------|---------|\n| CSS payload | ~10KB | ~5KB | **~50%** |\n| DOM nodes | Full tree | Pruned | **Fewer nodes** |\n| Selector matching | Classes | Inline styles | **Zero** |\n| Cache variants | `:desktop` | `:mobile` | **Device-specific** |\n\n### Response Headers\n\n```bash\n# Desktop request\ncurl -I https://affectively-app.taylorbuley.workers.dev/chat\n# x-device-type: desktop\n\n# Mobile request (iPhone)\ncurl -A \"iPhone\" -I https://affectively-app.taylorbuley.workers.dev/chat\n# x-device-type: mobile\n```\n\n### Key Files\n\n| File | Purpose |\n|------|---------|\n| `src/lib/device.ts` | Device detection from CF headers / User-Agent |\n| `src/lib/render.ts` | DOM pruning + inline styles in `renderTree()` |\n| `scripts/prerender-pages.ts` | Generates mobile/desktop CSS variants |\n| `src/lib/prerendered-pages.ts` | Generated CSS manifest with device variants |\n\n### DOM Pruning Rules\n\n```typescript\n// Mobile: prune desktop-only elements\n\"hidden md:block\"  → pruned (shows only on md+)\n\"hidden lg:flex\"   → pruned (shows only on lg+)\n\n// Desktop: prune mobile-only elements\n\"flex md:hidden\"   → pruned (hidden on md+)\n\"block lg:hidden\"  → pruned (hidden on lg+)\n```\n\n### Inline Styles\n\nCommon Tailwind classes are converted to inline styles at render time:\n\n```html\n<!-- Before (className) -->\n<div class=\"flex items-center justify-between w-full\">\n\n<!-- After (inline styles) -->\n<div style=\"display:flex;align-items:center;justify-content:space-between;width:100%\" class=\"remaining-classes\">\n```\n\nClasses converted: `flex`, `block`, `hidden`, `items-center`, `justify-between`, `w-full`, `h-full`, `relative`, `absolute`, `fixed`, and 30+ more.\n\n## Quick Start\n\n```bash\n# Development\nwrangler dev\n\n# Deploy\nwrangler deploy\n\n# Deploy to production\nwrangler deploy --env production\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `AEON_BACKEND_URL` | Aeon-Flux backend URL | `https://aeon-flux.taylorbuley.workers.dev` |\n| `ENVIRONMENT` | Runtime environment | `development` |\n| `CHAT_EDGE_URL` | Chat service URL | `https://chat-edge.affectively.dev` |\n| `FRACTAL_IAM_URL` | Fractal IAM sign-in base URL | `https://card.yoga` |\n| `API_EDGE_URL` | API edge fallback URL | `https://api-edge.affectively.ai` |\n| `FRACTAL_IAM_URL` | Fractal IAM URL used for delegated admin verification | `https://card.yoga` |\n\nDelegated admin notes:\n- `/api/admin/check/*`, `/api/admin/ai-settings`, and chat admin overrides accept short-lived IAM delegation proof and fail closed if verification cannot be completed.\n- Delegated `/api/user/preferences?targetUserId=...` access is limited to server-side D1 preferences. It does not bypass local-first `aeon-preferences` graphs or vault-like data.\n\n### Bindings\n\n| Binding | Type | Description |\n|---------|------|-------------|\n| `DB` | D1 Database | Edge web app database |\n| `AI` | Workers AI | AI model inference |\n| `CACHE` | KV Namespace | Page caching |\n| `API_EDGE` | Service | API route proxy (production) |\n| `AEON_FLUX` | Service | Aeon backend (production) |\n\n## Page Caching\n\nKV-based page cache with UCAN-aware user detection:\n\n| Cache Type | TTL | Description |\n|------------|-----|-------------|\n| Authenticated | 60s | Per-user cache keyed by UCAN token |\n| Anonymous | 300s | Shared cache for logged-out users |\n| Static | 3600s | Long-lived cache for legal/about pages |\n\n### Cache Keys\n\n```\npage:anonymous:/path     # Anonymous users\npage:user:<hash>:/path   # Authenticated users (UCAN hash)\n```\n\n## Project Structure\n\n```\nedge-web-app/\n├── src/\n│   ├── worker.ts           # Main worker entry point\n│   ├── lib/\n│   │   ├── types.ts        # Shared types and route definitions\n│   │   ├── render.ts       # Page rendering utilities\n│   │   └── cache.ts        # KV page caching\n│   └── api/                # API route handlers (legacy)\n├── public/                 # Static assets (client bundle)\n├── scripts/\n│   └── benchmark.ts        # Performance benchmark\n└── wrangler.toml           # Cloudflare configuration\n```\n\n## Benchmark\n\nRun performance benchmarks:\n\n```bash\n# Against local dev server\nwrangler dev &\nbun run scripts/benchmark.ts\n\n# Against deployed worker\nbun run scripts/benchmark.ts https://edge-web-app.taylorbuley.workers.dev\n```\n\n### Latest Results (with KV Cache)\n\n| Endpoint | Avg Latency | Throughput | Cache Improvement |\n|----------|-------------|------------|-------------------|\n| `/health` | 64ms | 111 req/s | N/A |\n| `/` | 54ms | 172 req/s | -16% |\n| `/chat` | 54ms | 167 req/s | -20% |\n| `/dashboard` | 56ms | 159 req/s | -8% |\n| `/login` | 57ms | 155 req/s | -7% |\n| `/about` | 56ms | 157 req/s | -13% |\n| `/privacy` | 56ms | 163 req/s | -8% |\n\n**Summary**: 100% success rate, ~55ms average latency, ~160 req/s throughput\n\n## Routes\n\n### Client-Hydrated Routes\n\nPages that use client-side React hydration:\n\n- **Core**: `/`, `/chat`, `/dashboard`\n- **Auth**: `/login`, `/onboarding`, `/verify-phone`\n- **Legal**: `/privacy`, `/terms`, `/cookies`, `/disclaimer`, `/refund-policy`, `/security`\n- **About**: `/about`, `/about/manifesto`, `/about/mission`, `/about/vision`, `/about/values`, `/about/principles`, `/about/thesis`\n- **Settings**: `/settings`, `/settings/privacy`, `/settings/contacts`, `/settings/calendar`, `/settings/soul-document`, `/settings/voice-shield`, `/settings/proactive`, `/settings/kardia`\n- **Tools**: `/breathing`, `/questions`, `/explore`, `/hud`\n- **Learning**: `/learning`, `/learning/breathing`, `/learning/mudras`, `/learning/emotion`\n- **Other**: `/chamber`, `/pensieve`, `/account`, `/account-settings`, `/contact`, `/pricing`, `/help`, `/faq`, `/download`, `/colophon`\n\n### API Routes\n\nAll `/api/*` routes are proxied to the `api-edge` service.\n\n## Development\n\n### Adding New Pages\n\n1. Add route to `CLIENT_ROUTES` in `src/lib/types.ts`:\n\n```typescript\n'/new-page': { rootId: 'new-page-root', title: 'New Page - AFFECTIVELY' },\n```\n\n2. Create the React component in the client bundle\n3. Deploy: `wrangler deploy`\n\n### Modifying Cache TTLs\n\nEdit `CACHE_TTL` in `src/lib/cache.ts`:\n\n```typescript\nconst CACHE_TTL = {\n  AUTHENTICATED: 60,    // Per-user cache\n  ANONYMOUS: 300,       // Shared cache\n  STATIC: 3600,         // Static pages\n};\n```\n\n### Testing Cache\n\n```bash\n# Check cache headers\ncurl -I https://edge-web-app.taylorbuley.workers.dev/about\n\n# Force cache bypass (authenticated request)\ncurl -H \"Authorization: Bearer <ucan-token>\" https://edge-web-app.taylorbuley.workers.dev/dashboard\n```\n\n## Environments\n\n| Environment | Worker Name | Domain |\n|-------------|-------------|--------|\n| Development | `edge-web-app-dev` | localhost:8787 |\n| Staging | `edge-web-app-staging` | staging URL |\n| Production | `affectively-app` | affectively.ai |\n\nDeploy to specific environment:\n\n```bash\nwrangler deploy --env staging\nwrangler deploy --env production\n```\n\n## ESI (Edge Side Inference) Integration\n\nThe edge-web-app uses ESI for personalized, emotion-aware rendering at the edge. ESI state is injected globally in the `<head>` and consumed by React components.\n\n### Global State Injection\n\n```javascript\n// Injected in <head> by the edge worker\nwindow.__AEON_ESI_STATE__ = {\n  userTier: 'pro',           // free | starter | pro | enterprise\n  emotionState: {\n    primary: 'focused',\n    valence: 0.3,\n    arousal: 0.6\n  },\n  preferences: {\n    theme: 'dark',\n    reducedMotion: false\n  },\n  sessionId: 'abc123',\n  localHour: 14,\n  timezone: 'America/New_York'\n};\n```\n\n### ESI Hooks\n\nAvailable hooks from `@affectively/aeon-flux/esi`:\n\n| Hook | Description | Returns |\n|------|-------------|---------|\n| `useGlobalESIState()` | Full ESI state object | `GlobalESIState` |\n| `useESITier()` | Current user tier | `'free' \\| 'starter' \\| 'pro' \\| 'enterprise'` |\n| `useESIEmotionState()` | Current emotional state | `{ primary, valence, arousal }` |\n| `useESIFeature(feature)` | Check if feature is enabled | `boolean` |\n\n### Tier-Based Feature Gating\n\n```typescript\nimport { useESITier } from '@affectively/aeon-flux/esi';\n\nfunction MyComponent() {\n  const userTier = useESITier();\n  const tierOrder = { free: 0, starter: 1, pro: 2, enterprise: 3 };\n\n  const hasPro = tierOrder[userTier] >= tierOrder['pro'];\n\n  return hasPro ? <ProFeature /> : <UpgradePrompt />;\n}\n```\n\n### Emotion-Aware Personalization\n\n```typescript\nimport { useESIEmotionState } from '@affectively/aeon-flux/esi';\nimport { useMemo } from 'react';\n\nfunction BreathingTool() {\n  const emotionState = useESIEmotionState();\n\n  const suggestion = useMemo(() => {\n    if (!emotionState?.primary) return null;\n\n    const suggestions = {\n      anxious: 'Try 4-7-8 breathing for calm',\n      stressed: 'Box breathing recommended',\n      focused: 'Maintain with rhythmic breathing'\n    };\n\n    return suggestions[emotionState.primary];\n  }, [emotionState]);\n\n  return suggestion && <Suggestion text={suggestion} />;\n}\n```\n\n### ESI Control Components (v1.2.0+)\n\nESI provides declarative control flow components for AI-powered conditional rendering:\n\n#### Boolean Visibility\n\n```tsx\nimport { ESI } from '@affectively/aeon-flux/esi';\n\n// Show/Hide based on AI-evaluated condition\n<ESI.Show condition=\"user appears stressed based on recent activity\">\n  <StressReliefSuggestion />\n</ESI.Show>\n\n<ESI.Hide condition=\"user is in a professional work context\">\n  <CasualGreeting />\n</ESI.Hide>\n\n// Shorthand versions\n<ESI.When is=\"user has completed onboarding\">\n  <WelcomeBack />\n</ESI.When>\n\n<ESI.Unless is=\"user is a new visitor\">\n  <ReturningUserContent />\n</ESI.Unless>\n```\n\n#### Gate Components\n\n```tsx\n// Gate by user tier (no AI call needed)\n<ESI.TierGate minTier=\"pro\">\n  <ProFeatures />\n</ESI.TierGate>\n\n// Gate by emotion state\n<ESI.EmotionGate allow={['calm', 'focused']} valenceRange={[0.2, 1]}>\n  <ProductivityTools />\n</ESI.EmotionGate>\n\n// Gate by time of day\n<ESI.TimeGate after={9} before={17} days={[1, 2, 3, 4, 5]}>\n  <WorkHoursContent />\n</ESI.TimeGate>\n```\n\n#### A/B Testing\n\n```tsx\n<ESI.ABTest\n  name=\"checkout-cta\"\n  variants={[\n    { id: 'a', content: <Button>Buy Now</Button> },\n    { id: 'b', content: <Button>Get Started</Button> },\n    { id: 'c', content: <Button>Subscribe</Button> }\n  ]}\n  selectionPrompt=\"Select variant that best matches user's browsing behavior\"\n  onSelect={(id) => analytics.track('ab_test', { name: 'checkout-cta', variant: id })}\n/>\n```\n\n#### Iteration and Selection\n\n```tsx\nimport { z } from 'zod';\n\n// AI-generated list iteration\n<ESI.ForEach\n  prompt=\"Generate 3 personalized workout suggestions\"\n  itemSchema={z.object({ name: z.string(), duration: z.number(), intensity: z.string() })}\n  render={(item, i) => <WorkoutCard key={i} {...item} />}\n  maxItems={5}\n/>\n\n// AI-driven option selection\n<ESI.Select\n  prompt=\"Which greeting style fits the user's mood?\"\n  options={[\n    { value: 'formal', label: 'Formal', content: <FormalGreeting /> },\n    { value: 'casual', label: 'Casual', content: <CasualGreeting /> },\n    { value: 'enthusiastic', label: 'Enthusiastic', content: <EnthusiasticGreeting /> }\n  ]}\n/>\n```\n\n#### Numeric Inference\n\n```tsx\n// Clamped numeric output\n<ESI.Clamp\n  prompt=\"Rate user engagement level\"\n  min={0}\n  max={100}\n  render={(value) => <EngagementMeter value={value} />}\n/>\n\n// Scored output with thresholds\n<ESI.Score\n  prompt=\"Assess content relevance for this user\"\n  render={(score, level) => (\n    <Badge color={level === 'high' ? 'green' : level === 'medium' ? 'yellow' : 'gray'}>\n      Relevance: {score}%\n    </Badge>\n  )}\n  thresholds={[40, 70]}\n/>\n```\n\n### Page Integration Checklist\n\nWhen adding ESI to a page:\n\n1. Import ESI hooks at the top of the file\n2. Add tier gating for premium features\n3. Add emotion-aware suggestions where applicable\n4. Show upgrade prompts for gated features\n5. Use `useMemo` for computed personalization\n6. Consider ESI control components for AI-driven conditional rendering\n\n## Related Services\n\n- **[aeon-flux](../aeon-flux)**: Page content backend with CRDT collaboration\n- **[api-edge](../api-edge)**: API routes (auth, chat, data)\n- **[relay-server](../relay-server)**: UCAN authentication relay\n- **[chat-edge](../chat-edge)**: AI chat service\n","readmeFilename":"README.md"}