Files
waggle-os/docs/briefs/2026-04-25-cc1-apps-www-nextjs-port-brief.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

519 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CC-1 Brief — Apps/www Next.js Port + DS Retrofit + Production Bootstrap
**Date**: 2026-04-25 (autored late evening 2026-04-24 dok Marko spava)
**Status**: Scope B ratified by Marko ("c da slazem se"), full port + bootstrap delegated
**Authorized by**: Marko Marković ("i sve sam guraj sad, sam odlucuj")
**PM**: claude-opus-4-7 (Cowork)
**Scope**: Migrate apps/www from Vite SPA to Next.js 14+ App Router, retrofit DS spec (Stage 1+2+3+light), bootstrap production infrastructure (auth, API routes, Stripe, analytics, i18n, SEO)
---
## §0 Pre-flight context
**Existing apps/www state** (auditован 2026-04-24):
Stack:
- Vite 6.3.5 + React 19.1.0 + TypeScript 5.9.3
- No Tailwind, no UI library — plain CSS sa custom properties u `src/styles/globals.css`
- Lucide-react za ikonice
- Vitest za testing
- npm scripts: `dev` / `build` / `preview` / `test` / `test:watch`
Komponente (sve u `src/components/`):
- `Navbar` — top nav
- `Hero` — h1 "AI Agents That Remember", 2 download CTAs (Windows + macOS GitHub releases), bee-orchestrator hero illustration, honey glow background
- `Features` — TBD audit
- `CrownJewels` — TBD audit
- `HowItWorks` — TBD audit
- `Pricing`**FUNCTIONAL Stripe checkout integration** sa 3 tiera (FREE/PRO $19/TEAMS $49), API endpoint `https://cloud.waggle-os.ai/api/stripe/create-checkout-session`, "Most Popular" badge na PRO
- `Enterprise` — TBD audit
- `BetaSignup` — TBD audit
- `BrandPersonasCard` — sa Vitest test coverage
- `Footer` — TBD audit
Stilski pristup: **inline styles sa CSS var refs** (e.g., `style={{ color: 'var(--honey-500)' }}`). CSS vars u `globals.css` već usklađeni sa DS Stage 1 dark tokens (hive-50 do hive-950, honey-300/400/500/600, status-ai/healthy, shadow-honey/elevated). **NEMA `[data-theme="light"]` block** — light mode treba dodati per DS Stage 4.
Public assets (`public/brand/`):
- 13 bee personas u light + dark variants (.png)
- 16 app icons u light + dark variants (.jpeg)
- hex-texture-light.png + hex-texture-dark.png
- logo.jpeg + logo-light.jpeg
- Backup folders sa older bee assets (mogu biti deleted post-port)
Test setup:
- `__tests__/setup.ts`
- `__tests__/BrandPersonasCard.test.tsx`
- `vitest.config.ts`
Routing: **single-page** (App.tsx renderuje sve komponente sequential, no React Router). Section IDs: hero, features, crown-jewels, how-it-works, pricing, enterprise, beta-signup, footer.
---
## §1 Migration target — Next.js 14 App Router
### §1.1 Stack target
```json
{
"name": "waggle-www",
"private": true,
"version": "0.3.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"next": "^15.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"lucide-react": "^0.577.0"
},
"devDependencies": {
"@types/node": "^22.x",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"typescript": "^5.9.3",
"vitest": "^2.x",
"@vitejs/plugin-react": "^4.7.0",
"happy-dom": "^15.x"
}
}
```
Ne koristiti Tailwind — postojeći CSS vars patternd radi i mapira na DS. Retain.
### §1.2 Folder structure target
```
apps/www/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout sa <html lang="en" data-theme="dark">
│ ├── page.tsx # Landing home (port App.tsx → server component sa client island-ima)
│ ├── globals.css # Import postojeći styles/globals.css + dodati [data-theme="light"] block
│ ├── pricing/
│ │ └── page.tsx # Standalone pricing page za direct linking
│ ├── api/
│ │ ├── stripe/
│ │ │ └── checkout/
│ │ │ └── route.ts # POST /api/stripe/checkout — replace external cloud.waggle-os.ai
│ │ ├── waitlist/
│ │ │ └── route.ts # POST /api/waitlist — beta signup endpoint
│ │ └── analytics/
│ │ └── route.ts # POST /api/analytics — anonymous event collection
│ ├── (legal)/
│ │ ├── privacy/page.tsx
│ │ ├── terms/page.tsx
│ │ └── cookies/page.tsx
│ ├── opengraph-image.png # OG fallback image
│ ├── twitter-image.png # Twitter card
│ ├── icon.png # Favicon
│ └── apple-icon.png
├── components/ # All client components (with "use client" header)
│ ├── Navbar.tsx
│ ├── Hero.tsx # Server component (static content)
│ ├── Features.tsx # Server component
│ ├── CrownJewels.tsx
│ ├── HowItWorks.tsx
│ ├── Pricing.tsx # CLIENT (useState za loading, useCallback za checkout)
│ ├── Enterprise.tsx
│ ├── BetaSignup.tsx # CLIENT (form state)
│ ├── BrandPersonasCard.tsx # Server (static render)
│ ├── Footer.tsx
│ ├── ThemeToggle.tsx # NEW — Auto/Light/Dark toggle, sets data-theme on <html>
│ └── CookieBanner.tsx # NEW — GDPR consent
├── lib/
│ ├── stripe.ts # Server-side Stripe client init
│ ├── analytics.ts # Client-side event tracking
│ ├── i18n.ts # Locale detection + helpers
│ └── theme.ts # data-theme persistence (localStorage waggle.theme)
├── data/
│ └── personas.ts # Move from src/data
├── public/
│ ├── brand/ # Same as before — assets kept
│ └── robots.txt # NEW
├── messages/ # i18n locale strings (English first)
│ └── en.json
├── middleware.ts # i18n routing + locale detection
├── next.config.mjs # Next.js config
├── tsconfig.json # Updated paths
├── vitest.config.ts # Adjust for Next.js
└── package.json
```
### §1.3 Per-component port plan
**Server components** (no "use client", static render):
- `Hero` — replace `<img src="brand/...">` with `next/image` for optimization. Add LCP priority hint.
- `Features` — verify static; if has hover state, mark as client.
- `CrownJewels` — same pattern.
- `HowItWorks` — same.
- `Enterprise` — same.
- `BrandPersonasCard` — uses static personas data; server component.
- `Footer` — static.
**Client components** (need "use client" directive):
- `Navbar` — likely has scroll-aware state, mobile menu toggle.
- `Pricing` — uses `useRef`, `useEffect` (IntersectionObserver), `useState` (loading), `useCallback` (Stripe checkout). All client-side. Update fetch URL from `https://cloud.waggle-os.ai/api/stripe/create-checkout-session` to **internal** `/api/stripe/checkout`.
- `BetaSignup` — form state, submission loading, success/error UI. Update endpoint to internal `/api/waitlist`.
- `ThemeToggle` (NEW) — `useState` + `useEffect` za localStorage sync + `document.documentElement.setAttribute('data-theme', mode)`.
- `CookieBanner` (NEW) — consent state.
### §1.4 Path mapping (asset references)
- `<img src="brand/bee-orchestrator-dark.png">``<Image src="/brand/bee-orchestrator-dark.png" width={176} height={176} priority />`
- All `public/brand/*` references stay relative to public root.
- CSS `url("data:image/svg+xml,...")` honeycomb pattern stays inline u globals.css.
---
## §2 DS retrofit — light/dark mode + Stage 4 tokens
### §2.1 globals.css augmentation
Postojeći `:root` block sadrži dark tokens. Dodaj `[data-theme="light"]` block sa Stage 4 light tokens:
```css
[data-theme="light"] {
--hive-950: #fafaf7; /* swap top */
--hive-900: #f0ede5;
--hive-850: #e8e3d6;
--hive-800: #d8d2c1;
--hive-700: #b8b0a0;
--hive-600: #8a8073;
--hive-500: #6b6359;
--hive-400: #4a443e;
--hive-300: #2c2724;
--hive-200: #1a1815;
--hive-100: #0e0c0a;
--hive-50: #1a1815; /* swap bottom — used as fg */
--honey-600: #8e6912; /* darker for AAA on light */
--honey-500: #b8821f; /* recalibrated AAA on cream */
--honey-400: #c4a418;
--honey-300: #e0c869;
--honey-glow: rgba(184, 130, 31, 0.15);
--honey-pulse: rgba(184, 130, 31, 0.08);
--status-ai: #6b46c1;
--status-healthy: #059669;
--shadow-honey: 0 0 24px rgba(184, 130, 31, 0.18), 0 0 4px rgba(184, 130, 31, 0.10);
--shadow-elevated: 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
}
```
### §2.2 Theme toggle component
`components/ThemeToggle.tsx`:
```tsx
"use client";
import { useState, useEffect } from 'react';
import { Sun, Moon, Monitor } from 'lucide-react';
type Mode = 'auto' | 'light' | 'dark';
export default function ThemeToggle() {
const [mode, setMode] = useState<Mode>('auto');
useEffect(() => {
const stored = localStorage.getItem('waggle.theme') as Mode | null;
setMode(stored ?? 'auto');
}, []);
useEffect(() => {
const apply = (m: Mode) => {
const effective = m === 'auto'
? (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
: m;
document.documentElement.setAttribute('data-theme', effective);
};
apply(mode);
localStorage.setItem('waggle.theme', mode);
if (mode === 'auto') {
const mq = window.matchMedia('(prefers-color-scheme: light)');
const handler = () => apply(mode);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}
}, [mode]);
return (
<div className="theme-toggle">
<button onClick={() => setMode('auto')} aria-label="Auto mode" data-active={mode === 'auto'}>
<Monitor size={16} />
</button>
<button onClick={() => setMode('light')} aria-label="Light mode" data-active={mode === 'light'}>
<Sun size={16} />
</button>
<button onClick={() => setMode('dark')} aria-label="Dark mode" data-active={mode === 'dark'}>
<Moon size={16} />
</button>
</div>
);
}
```
Place `<ThemeToggle />` u Navbar desno (visible on all pages).
### §2.3 Asset variant resolution
Bee personas, app icons, hex texture imaju light + dark variants. Dodati helper:
```tsx
import { useTheme } from '@/lib/theme';
const theme = useTheme();
const beeAsset = `/brand/bee-orchestrator-${theme}.png`;
```
Za Hero specifically: switch bee asset on theme change (orchestrator-dark vs orchestrator-light).
---
## §3 Bootstrap items — production infrastructure
### §3.1 API routes
#### `/api/stripe/checkout` (POST)
- Replace external `https://cloud.waggle-os.ai/api/stripe/create-checkout-session`
- Server-side init Stripe client sa `STRIPE_SECRET_KEY` env var
- Body: `{ tier: 'PRO' | 'TEAMS', billingPeriod: 'monthly' | 'annual' }`
- Returns: `{ url: string }` (Stripe Checkout Session URL)
- Error: `{ message: string }` 400/500
- Use Stripe Price IDs from env (`STRIPE_PRICE_PRO_MONTHLY`, `STRIPE_PRICE_TEAMS_MONTHLY`, etc.)
#### `/api/waitlist` (POST)
- Body: `{ email: string, persona?: string, source?: string }`
- Validate email format
- Store: forward to Resend / Supabase / Postmark — choose one (Marko: pick what's cheapest, suggest Resend for now)
- Returns: `{ success: boolean }`
- Rate limit: 5 requests/min per IP
#### `/api/analytics` (POST)
- Body: `{ event: string, properties: Record<string, unknown>, anonymous_id: string }`
- Anonymous events only (no PII unless user opted in via cookie consent)
- Forward to PostHog or Plausible — Marko pick (PostHog je in tools list, prefer)
- Returns: `{ accepted: boolean }`
### §3.2 Cookie consent + GDPR
`components/CookieBanner.tsx`:
- Show on first visit
- 3 options: "Accept all", "Necessary only", "Customize"
- Store consent: `localStorage.setItem('waggle.consent', JSON.stringify({...}))`
- Categories: necessary (always on), analytics (opt-in), marketing (opt-in)
- Render bottom-fixed sa backdrop blur na dark / soft drop shadow na light
### §3.3 Legal pages
`app/(legal)/privacy/page.tsx`, `terms/page.tsx`, `cookies/page.tsx`:
- Markdown-driven content (lib/markdown.ts helper)
- Static generation (export const dynamic = 'force-static')
- Linked from Footer + CookieBanner
Privacy text seed: koristi `pm-toolkit:privacy-policy` skill output kao starting draft, manual review pre publish-a.
### §3.4 SEO + metadata
`app/layout.tsx`:
```tsx
export const metadata: Metadata = {
metadataBase: new URL('https://waggle-os.ai'),
title: {
default: 'Waggle — AI Agents That Remember',
template: '%s · Waggle'
},
description: 'A workspace where AI agents remember your context, connect to your tools, and improve with every interaction. Desktop-native. Privacy-first. Local-first cognitive layer with bitemporal memory.',
keywords: ['AI memory', 'cognitive layer', 'local-first AI', 'EU AI Act', 'GDPR AI', 'bitemporal knowledge graph', 'MCP protocol', 'agent harness'],
authors: [{ name: 'Egzakta Group' }],
openGraph: {
type: 'website',
siteName: 'Waggle',
images: [{ url: '/opengraph-image.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
images: ['/twitter-image.png'],
creator: '@waggle_os',
},
robots: { index: true, follow: true },
alternates: {
canonical: '/',
languages: { 'en': '/' }, // expand on i18n rollout
},
};
```
`public/robots.txt`:
```
User-agent: *
Allow: /
Disallow: /api/
Sitemap: https://waggle-os.ai/sitemap.xml
```
`app/sitemap.ts`:
```tsx
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://waggle-os.ai', lastModified: new Date(), priority: 1.0 },
{ url: 'https://waggle-os.ai/pricing', lastModified: new Date(), priority: 0.8 },
{ url: 'https://waggle-os.ai/privacy', lastModified: new Date(), priority: 0.3 },
{ url: 'https://waggle-os.ai/terms', lastModified: new Date(), priority: 0.3 },
];
}
```
### §3.5 i18n scaffolding (English first)
Install `next-intl` (recommended for App Router):
```
pnpm add next-intl
```
Locale routing: `/[locale]/...` ali za MVP samo English live, infrastruktura ready za sledeće lokale (DE, FR, ES) bez code rewrite.
`middleware.ts`:
```ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en'],
defaultLocale: 'en',
});
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] };
```
`messages/en.json` — extract user-facing strings iz komponenti (Hero h1, CTA labels, pricing tier names, BetaSignup form copy). Per `feedback_i18n_landing_policy.md`: engleski first, locale-ready infra predefinisana.
### §3.6 Build + deploy
**Recommended host**: **Vercel** (zero-config Next.js, native preview deploys, edge runtime za API routes, integrated analytics)
Alternative: **Cloudflare Pages** (cheaper za high traffic, edge-first)
Env vars u Vercel dashboard:
- `STRIPE_SECRET_KEY`
- `STRIPE_PRICE_PRO_MONTHLY`
- `STRIPE_PRICE_TEAMS_MONTHLY`
- `STRIPE_WEBHOOK_SECRET`
- `RESEND_API_KEY` (ili Postmark/Supabase ekvivalent)
- `POSTHOG_PROJECT_KEY`
- `POSTHOG_HOST`
- `NEXT_PUBLIC_POSTHOG_KEY` (client-side init)
`next.config.mjs`:
```js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
formats: ['image/avif', 'image/webp'],
},
experimental: {
optimizePackageImports: ['lucide-react'],
},
};
export default nextConfig;
```
---
## §4 Wireframe v1.1 LOCKED gap analysis
Per `project_landing_wireframe_v11_locked_2026_04_22` memory, wireframe v1.1 specifies sections:
| Wireframe section | Existing component? | Action |
|---|---|---|
| Hero with KPI counter | Hero.tsx (no KPI counter yet) | **Augment** — add KPI counter sa "X memories preserved", "Y agents running", real-time API call ili static placeholder |
| Why-now hook | NOT PRESENT | **Add** new section between Hero and Features — narrative hook ("AI without memory is groundhog day") |
| Three-product columns | NOT PRESENT (Features may overlap) | **Add or refactor** — "hive-mind (OSS) / Waggle (consumer) / KVARK (enterprise)" 3-column architecture overview |
| How-it-works | HowItWorks.tsx | Verify content matches v1.1 spec |
| Pricing 3-tier | Pricing.tsx | ✓ Functional, **integrate with internal API route** |
| Personas grid 13 bees | BrandPersonasCard.tsx (single card) | **Augment** — add full 13-grid section pre BetaSignup |
| FAQ | NOT PRESENT | **Add** new FAQ section (5-7 questions: pricing, privacy, EU AI Act, MCP, OSS) |
| Founder note | NOT PRESENT | **Add** "From the team" section with Marko's photo + short narrative |
| CTA | Hero CTAs + Pricing CTAs + BetaSignup | Sufficient |
| Footer | Footer.tsx | Verify links: Privacy, Terms, Cookies, Twitter, GitHub, Discord (TBD) |
---
## §5 Test migration
Existing `__tests__/BrandPersonasCard.test.tsx` + `__tests__/setup.ts` keep but adapt:
- Vitest config update (Next.js compatibility, may need `vitest-environment-vitest-pure` or `happy-dom`)
- Add component tests for: ThemeToggle, CookieBanner, Pricing checkout flow (mock Stripe), BetaSignup submission
- Add API route integration tests sa MSW (Mock Service Worker)
---
## §6 Sequential execution plan (12 commits suggested)
Commit 1: `[port] init Next.js 15 scaffold, retain Vite parallel for rollback`
Commit 2: `[port] migrate components to Next.js with use client where needed`
Commit 3: `[port] move src/styles → app/globals.css + light mode tokens block`
Commit 4: `[port] ThemeToggle component + integrate u Navbar`
Commit 5: `[port] API routes — stripe checkout, waitlist, analytics`
Commit 6: `[port] middleware.ts + i18n scaffolding (en only live)`
Commit 7: `[port] legal pages (privacy/terms/cookies) + CookieBanner`
Commit 8: `[port] SEO metadata + sitemap + robots + OG/Twitter images`
Commit 9: `[port] add wireframe v1.1 missing sections (WhyNow + ThreeProducts + PersonasGrid + FAQ + FounderNote)`
Commit 10: `[port] update tests + add new component coverage`
Commit 11: `[port] build verification + Vercel deploy config`
Commit 12: `[port] retire Vite scaffolding (delete vite.config.ts, src/main.tsx etc.) — POST-VERIFICATION`
Commit 1-11: incremental, ne bocе vam working state.
Commit 12: cleanup, tek posle visual + functional QA na deployed staging URL.
---
## §7 Halt criteria + budget
- **Budget**: $0 (no API calls during port — codegen only). Stripe + Resend + PostHog incur $0 setup, pay-per-use after launch.
- **Wall-clock**: 8-12h CC-1 work realistic za scratch port + bootstrap. Marko može da paste-uje brief u CC-1 kad SOTA padne, paralelno sa Gate D exit + claim-narrative work.
- **Halt triggers**:
- Vite cleanup (Commit 12) — only after staging deploy verified by Marko visual review
- Live env vars (Stripe Production keys) — Marko populates u Vercel dashboard, not in code
- Domain DNS cutover (waggle-os.ai → Vercel) — Marko handles
- **Rollback**: Commits 1-11 keep Vite scaffolding present, can `git revert` and rebuild Vite if Next.js port has unforeseen issue. Commit 12 is one-way.
---
## §8 PM signals to watch
- "use client" overuse (every component) — sign of confused server/client split, code review pass needed
- CSS regression na light mode toggle — visual diff vs DS Stage 4 spec
- Stripe checkout flow break — manual test sa test card 4242 4242 4242 4242 pre prod
- SEO meta validation — `next build` output check za canonical URLs, OG images
- Lighthouse score baseline — target ≥90 Performance, ≥95 Accessibility, ≥100 Best Practices, ≥95 SEO
---
## §9 Authorized by
PM Marko Marković, 2026-04-24 evening, full execution delegated ("i sve sam guraj sad, sam odlucuj, ja odoh da spavam citam ujutro sta si sve uradio").
CC-1 može da počne kad benchmark završi i SOTA narrative ratifikuje, ili paralelno ako ima dovoljno context-a (Vite stack je read-only audit, port je clean greenfield Next.js).
Brief je deterministic — sve odluke arhitekture su uzete u brief-u, no clarifying questions needed.
---
## §10 Companion deliverables (paralelno sa ovim brief-om)
PM (claude-opus-4-7) overnight produces:
1. `briefs/2026-04-25-launch-comms-templates.md` — multi-asset suite (technical blog post, LinkedIn, Twitter, hive-mind announcement, waitlist email, press kit)
2. `briefs/e2e-persona-tests/2026-04-25-e2e-persona-test-matrix.md` — 9-archetype × scenarios test matrix (3 monetization tier × 3 user proficiency)
3. `decisions/2026-04-25-overnight-pm-execution-log.md` — what was completed overnight, decision rationale, open items za Marko ujutru
Sve čekaju Marka u 2026-04-25 ujutru za review.