diff --git a/.claude/launch.json b/.claude/launch.json index 5ffcfcaf8..9c61833b2 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -25,6 +25,18 @@ "runtimeExecutable": "/bin/bash", "runtimeArgs": ["-c", "export PATH=/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH && npx nx serve examples-chat-angular --port 4400"], "port": 4400 + }, + { + "name": "genui-python", + "runtimeExecutable": "/bin/bash", + "runtimeArgs": ["-c", "set -a && source /Users/blove/repos/angular-agent-framework/.env && set +a && cd cockpit/chat/generative-ui/python && uv run langgraph dev --port 5508 --no-browser"], + "port": 5508 + }, + { + "name": "genui-angular", + "runtimeExecutable": "/bin/bash", + "runtimeArgs": ["-c", "export PATH=/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH && npx nx serve cockpit-chat-generative-ui-angular --port 4500"], + "port": 4500 } ] } diff --git a/apps/website/e2e/website.spec.ts b/apps/website/e2e/website.spec.ts index affefc2bf..6ee385c18 100644 --- a/apps/website/e2e/website.spec.ts +++ b/apps/website/e2e/website.spec.ts @@ -148,9 +148,9 @@ test('api reference renders in docs', async ({ page }) => { await expect(page.locator('article').first()).toBeVisible(); }); -test('nav has pricing link', async ({ page }) => { +test('footer has pricing link', async ({ page }) => { await page.goto('/'); - await expect(page.locator('nav a[href="/pricing"]').first()).toBeVisible(); + await expect(page.locator('footer a[href="/pricing"]').first()).toBeVisible(); }); test('mobile viewport renders nav', async ({ page }) => { diff --git a/apps/website/public/screenshots/canonical-demo-generative-ui.webp b/apps/website/public/screenshots/canonical-demo-generative-ui.webp new file mode 100644 index 000000000..fdfec23cf Binary files /dev/null and b/apps/website/public/screenshots/canonical-demo-generative-ui.webp differ diff --git a/apps/website/src/app/opengraph-image.tsx b/apps/website/src/app/opengraph-image.tsx index 76f7269ba..6c14de15f 100644 --- a/apps/website/src/app/opengraph-image.tsx +++ b/apps/website/src/app/opengraph-image.tsx @@ -142,8 +142,8 @@ export default async function OpenGraphImage() { >
{POSITIONING_PROOF_POINTS.slice(0, 3).map((proofPoint, index) => ( - - {proofPoint} + + {proofPoint.label} ))}
diff --git a/apps/website/src/components/landing/Differentiator.spec.tsx b/apps/website/src/components/landing/Differentiator.spec.tsx new file mode 100644 index 000000000..c9734b948 --- /dev/null +++ b/apps/website/src/components/landing/Differentiator.spec.tsx @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +// @vitest-environment jsdom +import React from 'react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { Differentiator } from './Differentiator'; + +vi.mock('../../lib/analytics/client', () => ({ + trackCtaClick: vi.fn(), + trackExternalLinkClick: vi.fn(), +})); + +vi.mock('../ui/Container', () => ({ + Container: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); +vi.mock('../ui/Section', () => ({ + Section: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); +vi.mock('../ui/Eyebrow', () => ({ + Eyebrow: ({ children }: { children: React.ReactNode }) => {children}, +})); +vi.mock('@ngaf/design-tokens', () => ({ + tokens: { + colors: { + accent: '#7c3aed', + textPrimary: '#111827', + textSecondary: '#6b7280', + textMuted: '#9ca3af', + }, + surfaces: { + border: '#e5e7eb', + }, + typography: { + h2: { family: 'sans-serif', size: '2rem', line: '1.2' }, + bodyLg: { family: 'sans-serif', size: '1.125rem', line: '1.6' }, + body: { family: 'sans-serif', size: '1rem', line: '1.5' }, + fontMono: 'monospace', + }, + }, +})); + +import { trackCtaClick } from '../../lib/analytics/client'; + +const EXPECTED_NEEDS = [ + 'Durable threads', + 'Resumable interrupts', + 'Tool calls as events', + 'Streaming state as signals', + 'Generative UI on your design system', + 'Recoverable errors', + 'Backend portability', + 'Angular-native', + 'Observability hooks', + 'MIT + self-hosted', +]; + +describe('Differentiator', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders the section headline', () => { + render(); + expect( + screen.getByRole('heading', { + level: 2, + name: 'Everything an Angular agent needs once the demo works.', + }), + ).toBeTruthy(); + }); + + it('renders all 10 production-readiness rows', () => { + render(); + for (const need of EXPECTED_NEEDS) { + expect(screen.getByText(need)).toBeTruthy(); + } + }); + + it('renders the @ngaf/render primitive for the generative UI row', () => { + render(); + expect(screen.getByText('@ngaf/render')).toBeTruthy(); + }); + + it('links the footer CTA to /pilot-to-prod', () => { + render(); + const link = screen.getByRole('link', { name: /Pilot to Prod/ }); + expect(link.getAttribute('href')).toBe('/pilot-to-prod'); + }); + + it('fires the home_why_pilot_to_prod CTA event when the footer link is clicked', () => { + render(); + const link = screen.getByRole('link', { name: /Pilot to Prod/ }); + fireEvent.click(link); + expect(trackCtaClick).toHaveBeenCalledWith({ + surface: 'home', + destination_url: '/pilot-to-prod', + cta_id: 'home_why_pilot_to_prod', + cta_text: 'Pilot to Prod', + }); + }); +}); diff --git a/apps/website/src/components/landing/Differentiator.tsx b/apps/website/src/components/landing/Differentiator.tsx index f0e80bf35..e4f871062 100644 --- a/apps/website/src/components/landing/Differentiator.tsx +++ b/apps/website/src/components/landing/Differentiator.tsx @@ -1,61 +1,97 @@ -import type { ReactNode } from 'react'; +'use client'; + import { tokens } from '@ngaf/design-tokens'; import { Container } from '../ui/Container'; import { Section } from '../ui/Section'; import { Eyebrow } from '../ui/Eyebrow'; -import { Card } from '../ui/Card'; +import { trackCtaClick } from '../../lib/analytics/client'; -interface PositionCard { - eyebrow: string; - headline: string; - body: ReactNode; +interface ProductionRow { + need: string; + description: string; + primitive: string; } -const CARDS: PositionCard[] = [ +const PRODUCTION_ROWS: ProductionRow[] = [ + { + need: 'Durable threads', + description: 'Persist across reloads, resume, branch, replay.', + primitive: 'threadId signal + durable transports', + }, + { + need: 'Resumable interrupts', + description: 'Human-in-the-loop pause, resume token, retry, cancel.', + primitive: 'interrupt(), resume()', + }, + { + need: 'Tool calls as events', + description: 'Stream progress, structured args, surfaced errors.', + primitive: 'tool events on agent()', + }, { - eyebrow: 'Runtime layer', - headline: 'One Angular contract for every agent runtime.', - body: 'Wire LangGraph, AG-UI, CrewAI, Mastra, Pydantic AI, AWS Strands, or your own backend behind the same Angular primitives.', + need: 'Streaming state as signals', + description: 'messages(), status(), error() — not promises.', + primitive: 'signal-native agent()', }, { - eyebrow: 'Streaming state', - headline: 'Messages, status, errors, and tools as signals.', - body: ( - <> - agent() exposes token streams, interrupts, tool progress, branch history,{' '} - error(),{' '} - status(), and{' '} - reload(). - - ), + need: 'Generative UI on your design system', + description: 'Vercel json-render + Google A2UI rendered into your Angular components.', + primitive: '@ngaf/render', }, { - eyebrow: 'Generative UI', - headline: 'Agent output renders into your component system.', - body: ( - <> - Render Vercel json-render and Google A2UI specs into Angular components you already own. - - ), + need: 'Recoverable errors', + description: 'Retry, reload, error boundaries, fallback content.', + primitive: 'error(), reload()', }, { - eyebrow: 'Production surface', - headline: 'The pieces that move a demo into production.', - body: 'Fallbacks, reloads, persistence patterns, observability hooks, and MIT-licensed primitives you can own long term.', + need: 'Backend portability', + description: 'LangGraph today; AG-UI / Mastra / CrewAI / your own tomorrow — same UI.', + primitive: 'runtime adapters behind one contract', + }, + { + need: 'Angular-native', + description: 'DI, signals, RxJS interop — no React rewrite.', + primitive: 'built on Angular primitives, not ported', + }, + { + need: 'Observability hooks', + description: 'Tracing seams; app telemetry off by default.', + primitive: 'event hooks, opt-in only', + }, + { + need: 'MIT + self-hosted', + description: 'Own the primitives long-term, no vendor lock-in.', + primitive: 'MIT-licensed, no runtime SaaS dependency', }, ]; +function CheckIcon() { + return ( + + ); +} + export function Differentiator() { return (
- {/* Editorial top */}
Why this exists

- The fullstack agentic library for Angular. + Everything an Angular agent needs once the demo works.

- NGAF brings the pieces of an agentic product into one Angular-first SDK: runtime adapters, signal-native streaming, tool events, generative UI, and production patterns. It is built from real agent UI experience, not a thin integration layer. + A streaming chat tutorial takes an hour. Shipping a real agent — durable, interruptible, observable, on your design system — takes most teams six months. NGAF gives the Angular surface that the rest of the stack assumes you've already built.

- {/* 4-card sub-grid */} -
- {CARDS.map((c) => ( - - {c.eyebrow} -

- {c.headline} -

-

( +

  • + +
    - {c.body} -

    - +
    + + {row.need} + + + {row.description} + +
    + + {row.primitive} + +
    +
  • ))} -
    + + +

    + Want help walking these on your codebase?{' '} + + trackCtaClick({ + surface: 'home', + destination_url: '/pilot-to-prod', + cta_id: 'home_why_pilot_to_prod', + cta_text: 'Pilot to Prod', + }) + } + style={{ color: tokens.colors.accent, textDecoration: 'none', fontWeight: 600 }} + > + Pilot to Prod → + +

    + +
    ); diff --git a/apps/website/src/components/landing/Hero.spec.tsx b/apps/website/src/components/landing/Hero.spec.tsx index 2a18585df..285ad959f 100644 --- a/apps/website/src/components/landing/Hero.spec.tsx +++ b/apps/website/src/components/landing/Hero.spec.tsx @@ -53,7 +53,7 @@ describe('Hero', () => { .toMatch(/Ship production agent UIs in Angular\./); expect(screen.getByText(HERO_SUBHEAD)).toBeTruthy(); for (const proofPoint of POSITIONING_PROOF_POINTS) { - expect(screen.getByText(proofPoint)).toBeTruthy(); + expect(screen.getByText(proofPoint.label)).toBeTruthy(); } }); diff --git a/apps/website/src/components/landing/Hero.tsx b/apps/website/src/components/landing/Hero.tsx index 155371a5d..b1db984e3 100644 --- a/apps/website/src/components/landing/Hero.tsx +++ b/apps/website/src/components/landing/Hero.tsx @@ -121,9 +121,22 @@ export function Hero() { }} > {POSITIONING_PROOF_POINTS.map((proofPoint, index) => ( - - {proofPoint} - + + track(analyticsEvents.marketingCtaClick, { + cta_id: 'hero_proof_pill', + track: 'developer', + surface: 'home', + }) + } + style={{ textDecoration: 'none' }} + > + + {proofPoint.label} + + ))}

    - {/* Right column — layered collage (preserved verbatim from prior Hero.tsx) */} -

    @@ -201,6 +221,27 @@ a.status();`} gap: 40px !important; } } + .hero-proof-pill { + transition: transform 160ms ease, background 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease; + will-change: transform; + } + a:hover > .hero-proof-pill, + a:focus-visible > .hero-proof-pill { + transform: translateY(-1px); + background: ${tokens.colors.accentSurface} !important; + border-color: ${tokens.colors.accentBorder} !important; + color: ${tokens.colors.accent} !important; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); + } + a:active > .hero-proof-pill { + transform: translateY(0); + box-shadow: none; + } + @media (prefers-reduced-motion: reduce) { + .hero-proof-pill { transition: none; } + a:hover > .hero-proof-pill, + a:focus-visible > .hero-proof-pill { transform: none; } + } `} diff --git a/apps/website/src/components/shared/Footer.tsx b/apps/website/src/components/shared/Footer.tsx index a5dec905a..85ff25908 100644 --- a/apps/website/src/components/shared/Footer.tsx +++ b/apps/website/src/components/shared/Footer.tsx @@ -260,6 +260,12 @@ export function Footer() { onMouseLeave={(e) => (e.currentTarget.style.color = tokens.colors.textSecondary)}> Customer Support + trackFooterCta('All Solutions', '/solutions')} + onMouseEnter={(e) => (e.currentTarget.style.color = tokens.colors.accent)} + onMouseLeave={(e) => (e.currentTarget.style.color = tokens.colors.textSecondary)}> + All solutions + {/* Resources column */} @@ -271,6 +277,12 @@ export function Footer() { onMouseLeave={(e) => (e.currentTarget.style.color = tokens.colors.textSecondary)}> Getting Started + trackFooterCta('Blog', '/blog')} + onMouseEnter={(e) => (e.currentTarget.style.color = tokens.colors.accent)} + onMouseLeave={(e) => (e.currentTarget.style.color = tokens.colors.textSecondary)}> + Blog + trackCtaClick({ surface: 'nav', - destination_url: '/pilot-to-prod#whitepaper-block', - cta_id: 'nav_get_started', - cta_text: 'Get Started', + destination_url: '/contact', + cta_id: 'nav_talk_to_us', + cta_text: 'Talk to Us', })} > - Get Started + Talk to Us @@ -324,19 +320,19 @@ export function Nav() { diff --git a/apps/website/src/lib/analytics/events.ts b/apps/website/src/lib/analytics/events.ts index dce89fab0..2ce0c88da 100644 --- a/apps/website/src/lib/analytics/events.ts +++ b/apps/website/src/lib/analytics/events.ts @@ -50,9 +50,14 @@ export type CtaId = // Hero (Spec 2) | 'hero_install' | 'hero_talk_to_engineers' + | 'hero_demo_open_cockpit' + | 'hero_demo_open_cockpit_caption' + | 'hero_proof_pill' // Whitepaper block on home | 'home_whitepaper_direct' | 'home_whitepaper_direct_inline' + // Why this exists section + | 'home_why_pilot_to_prod' // Announcement toast | 'toast_get_guide' | 'toast_direct_download' diff --git a/apps/website/src/lib/positioning.ts b/apps/website/src/lib/positioning.ts index fc9750cf6..0b18130ed 100644 --- a/apps/website/src/lib/positioning.ts +++ b/apps/website/src/lib/positioning.ts @@ -1,13 +1,21 @@ -export const PRIMARY_TAGLINE = 'Agent UI for Angular — Production-ready chat, threads, and generative UI for AI agents.'; -export const LONG_SUBHEAD = 'The enterprise-grade Angular UI framework for LangGraph and AG-UI-compatible agents: headless chat, durable threads, interrupts, subagents, planning, memory, and generative UI through json-render and A2UI-compatible specs.'; -export const HERO_SUBHEAD = 'Build enterprise-grade agent experiences in Angular: headless chat, durable threads, interrupts, subagents, planning, memory, and generative UI for LangGraph, AG-UI, json-render, and A2UI-compatible backends.'; -export const POSITIONING_PROOF_POINTS = [ - 'LangGraph + AG-UI', - 'Durable threads', - 'Interrupts', - 'Subagents', - 'Planning + memory', - 'json-render + A2UI', +export const PRIMARY_TAGLINE = + 'Agent UI for Angular. Durable threads, interrupts, subagents, planning, memory, and generative UI.'; +export const LONG_SUBHEAD = + 'The fullstack agentic Angular framework for LangGraph and AG-UI-compatible agents: durable threads, interrupts, subagents, planning, memory, and generative UI using Vercel json-render and Google A2UI.'; +export const HERO_SUBHEAD = `Build fullstack agentic apps in Angular with: durable threads, interrupts, subagents, planning, memory, and generative UI using Vercel json-render and Google A2UI.`; +export interface PositioningProofPoint { + readonly label: string; + readonly href: string; +} + +export const POSITIONING_PROOF_POINTS: readonly PositioningProofPoint[] = [ + { label: 'LangGraph + AG-UI', href: '/docs/agent/concepts/langgraph-basics' }, + { label: 'Durable threads', href: '/docs/agent/guides/persistence' }, + { label: 'Interrupts', href: '/docs/agent/guides/interrupts' }, + { label: 'Subagents', href: '/docs/agent/guides/subgraphs' }, + { label: 'Planning + memory', href: '/docs/agent/guides/memory' }, + { label: 'json-render + A2UI', href: '/docs/render/concepts/json-render-vs-a2ui' }, ] as const; -export const SHORT_POSITIONING_DESCRIPTION = 'Production-ready chat, durable threads, interrupts, subagents, planning, memory, and generative UI for Angular agent apps.'; +export const SHORT_POSITIONING_DESCRIPTION = + 'Production-ready chat, durable threads, interrupts, subagents, planning, memory, and generative UI for agentic Angular apps.'; export const DEFAULT_META_DESCRIPTION = SHORT_POSITIONING_DESCRIPTION; diff --git a/apps/website/src/lib/site-metadata.spec.ts b/apps/website/src/lib/site-metadata.spec.ts index 990e52f54..35e191a8e 100644 --- a/apps/website/src/lib/site-metadata.spec.ts +++ b/apps/website/src/lib/site-metadata.spec.ts @@ -11,12 +11,12 @@ import { describe('site positioning copy', () => { it('exports the approved primary tagline and supporting copy', () => { - expect(PRIMARY_TAGLINE).toBe('Agent UI for Angular — Production-ready chat, threads, and generative UI for AI agents.'); - expect(LONG_SUBHEAD).toContain('enterprise-grade Angular UI framework'); + expect(PRIMARY_TAGLINE).toBe('Agent UI for Angular. Durable threads, interrupts, subagents, planning, memory, and generative UI.'); + expect(LONG_SUBHEAD).toContain('fullstack agentic Angular framework'); expect(LONG_SUBHEAD).toContain('LangGraph and AG-UI-compatible agents'); - expect(LONG_SUBHEAD).toContain('json-render and A2UI-compatible specs'); - expect(HERO_SUBHEAD).toContain('headless chat, durable threads, interrupts, subagents, planning, memory'); - expect(POSITIONING_PROOF_POINTS).toEqual([ + expect(LONG_SUBHEAD).toContain('Vercel json-render and Google A2UI'); + expect(HERO_SUBHEAD).toContain('durable threads, interrupts, subagents, planning, memory, and generative UI'); + expect(POSITIONING_PROOF_POINTS.map((p) => p.label)).toEqual([ 'LangGraph + AG-UI', 'Durable threads', 'Interrupts', @@ -24,6 +24,14 @@ describe('site positioning copy', () => { 'Planning + memory', 'json-render + A2UI', ]); + expect(POSITIONING_PROOF_POINTS.map((p) => p.href)).toEqual([ + '/docs/agent/concepts/langgraph-basics', + '/docs/agent/guides/persistence', + '/docs/agent/guides/interrupts', + '/docs/agent/guides/subgraphs', + '/docs/agent/guides/memory', + '/docs/render/concepts/json-render-vs-a2ui', + ]); expect(DEFAULT_META_DESCRIPTION).toBe(SHORT_POSITIONING_DESCRIPTION); }); diff --git a/docs/superpowers/plans/2026-05-19-hero-collage-simplification.md b/docs/superpowers/plans/2026-05-19-hero-collage-simplification.md new file mode 100644 index 000000000..cbc8d1442 --- /dev/null +++ b/docs/superpowers/plans/2026-05-19-hero-collage-simplification.md @@ -0,0 +1,130 @@ +# Hero Collage Simplification Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the two-frame hero collage in `apps/website/src/components/landing/Hero.tsx` with a single `BrowserFrame` containing the generative-UI dashboard at 100% column width, eliminating the `agent.signal()` code window that occludes the dashboard's lower-right quadrant. + +**Architecture:** Edit only the right column of the existing `Hero` component. Delete the second `BrowserFrame` (the `
    ` code snippet). Strip the absolute-positioning wrapper styles (`position: relative`, `minHeight: 420`) and the inline absolute positioning on the remaining frame. Keep all left-column markup, the grid layout, and the responsive `@media` block exactly as-is.
    +
    +**Tech Stack:** Next.js 16, React 19, TypeScript, `@ngaf/design-tokens`, the project's `BrowserFrame` UI primitive.
    +
    +**Reference:** Spec at `docs/superpowers/specs/2026-05-19-hero-collage-simplification-design.md`.
    +
    +---
    +
    +## File map
    +
    +- **Modify:** `apps/website/src/components/landing/Hero.tsx`
    +  - Delete the second `BrowserFrame` (the `agent.signal()` code snippet block).
    +  - Remove `position: 'relative'`, `minHeight: 420` from the right-column wrapper `
    `. + - Remove `position: 'absolute', top: 0, left: 0` from the remaining `BrowserFrame`'s `style`. Change `width: '92%'` to `width: '100%'`. + - Leave all other JSX, the `@keyframes blink`/`@media` `` block (with `@keyframes blink` and the `@media (max-width: 900px) { .hero-grid { ... } }` rule), and the closing `` all stay exactly as-is. + +- [ ] **Step 2: Verify no stale imports** + +Run: `grep -n "fontMono" apps/website/src/components/landing/Hero.tsx` + +Expected: no matches. The deleted `
    ` block used `tokens.typography.fontMono`; after this change the right column no longer references it.
    +
    +If a match remains, you missed part of the deletion — re-check Step 1.
    +
    +Run: `grep -n "agent.signal\|provideAgent" apps/website/src/components/landing/Hero.tsx`
    +
    +Expected: no matches. (These strings only existed inside the deleted `
    ` block.)
    +
    +- [ ] **Step 3: Type-check**
    +
    +Run: `npx tsc -p apps/website/tsconfig.json --noEmit 2>&1 | grep -i Hero.tsx || echo "ok"`
    +
    +Expected: `ok`.
    +
    +- [ ] **Step 4: Visual check on the running dev server**
    +
    +The website-dev preview server is running on `http://localhost:3000`. Reload the page and confirm:
    +
    +- The hero right column shows a single `BrowserFrame` with the airline operations dashboard fully visible — KPI cards, line chart, bar chart, and disruptions table all readable.
    +- No code window appears anywhere in the hero.
    +- No other element overlaps the dashboard.
    +
    +(If using `mcp__Claude_Preview__preview_screenshot`, capture at viewport 1280×820 and 375×812.)
    +
    +- [ ] **Step 5: Run the website unit tests**
    +
    +Run from `apps/website/`: `npx vitest run 2>&1 | tail -8`
    +
    +Expected baseline: same as before this change — 53 passing, 2 pre-existing failures in `docs.spec.ts` and `open-in-cockpit.spec.tsx` (both confirmed to fail on `main` too; not caused by this work).
    +
    +If a previously-passing test in `Hero.spec.*` newly fails because it asserts the presence of the deleted `
    ` block or the second `BrowserFrame`, update the test to match the new structure. **Note:** at plan-writing time, no `apps/website/src/components/landing/Hero.spec.*` file exists. If it still doesn't exist when you run the tests, you have nothing to update.
    +
    +- [ ] **Step 6: Commit**
    +
    +```bash
    +git add apps/website/src/components/landing/Hero.tsx
    +git commit -m "$(cat <<'EOF'
    +feat(website): drop hero code window so generative UI screenshot owns the column
    +
    +The agent.signal() 
     frame was occluding the dashboard's bar chart and
    +disruptions table — the densest, most proof-heavy region of the new hero
    +screenshot. Removes it and resizes the dashboard frame to 100% column width.
    +provideAgent / agent() still appear in the install CTA, the "Why this exists"
    +primitive labels, and every install snippet in /docs.
    +
    +See docs/superpowers/specs/2026-05-19-hero-collage-simplification-design.md.
    +
    +Co-Authored-By: Claude Opus 4.7 (1M context) 
    +EOF
    +)"
    +```
    +
    +---
    +
    +## Self-review
    +
    +**Spec coverage:**
    +- Drop second `BrowserFrame` — Task 1 Step 1, plus Step 2 grep verifications. ✓
    +- Remove `position: relative` and `minHeight: 420` from wrapper — Task 1 Step 1 (the replacement `
    ` has no inline style). ✓ +- Remove absolute positioning from remaining frame, change width to 100% — Task 1 Step 1. ✓ +- Keep grid layout, mobile collapse, left column, image asset, alt text, `aria-hidden` — Task 1 Step 1 leaves all of those untouched. ✓ +- Tests stay green — Task 1 Step 5. ✓ +- Visual verification at desktop and mobile — Task 1 Step 4. ✓ + +**Placeholder scan:** no TBD/TODO; every step includes the literal code or command and expected result. + +**Type consistency:** the only symbol referenced is `BrowserFrame` (unchanged import), the `url` / `rotate` / `elevation` props (unchanged on the kept frame), and standard JSX. No new types. + +Plan complete. diff --git a/docs/superpowers/plans/2026-05-19-why-this-exists-section.md b/docs/superpowers/plans/2026-05-19-why-this-exists-section.md new file mode 100644 index 000000000..9bb15e029 --- /dev/null +++ b/docs/superpowers/plans/2026-05-19-why-this-exists-section.md @@ -0,0 +1,484 @@ +# "Why this exists" section redesign — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the 4-card "Why this exists" section in `apps/website/src/components/landing/Differentiator.tsx` with a 10-row production-readiness checklist that dramatizes the demos→production gap. + +**Architecture:** Single React Server Component rewrite. No new files except a co-located spec test. Section keeps its position on the landing page; only its internal content + layout change. Pulls data from a local `PRODUCTION_ROWS` array; renders each row as a flex layout `[check icon] [bold need] [muted description] [mono primitive]` with hairline dividers. Footer "Pilot to Prod" link is a tracked CTA. + +**Tech Stack:** Next.js 16 (Turbopack), React 19, TypeScript, `@ngaf/design-tokens`, Vitest (`vite.config.mts`), React Testing Library (project already uses `@testing-library/react` per existing landing specs), `apps/website/src/lib/analytics/client.ts` (`trackCtaClick`). + +**Reference:** Spec at `docs/superpowers/specs/2026-05-19-why-this-exists-section-design.md`. + +--- + +## File map + +- **Modify:** `apps/website/src/components/landing/Differentiator.tsx` + - Drop existing `CARDS` array, drop 4-card grid. + - Add `PRODUCTION_ROWS` array (10 entries). + - Add inline `CheckIcon` SVG component. + - Update headline + subhead + add footer link with `trackCtaClick`. + - Convert from server component (currently no `'use client'`) to client component — needed because `trackCtaClick` runs on click. +- **Create:** `apps/website/src/components/landing/Differentiator.spec.tsx` + - Vitest + React Testing Library. + - Asserts headline text, all 10 rows render with need/description/primitive, footer link points to `/pilot-to-prod`, click fires `trackCtaClick`. + +No changes to `positioning.ts`, `Nav.tsx`, `Hero.tsx`, `Section.tsx`, `Container.tsx`, `Eyebrow.tsx`, or any analytics events file (event shape is generic `trackCtaClick`, no new event type needed). + +--- + +## Task 1: Update Differentiator.tsx — content & structure + +**Files:** +- Modify: `apps/website/src/components/landing/Differentiator.tsx` + +- [ ] **Step 1: Replace the file contents** + +Overwrite `apps/website/src/components/landing/Differentiator.tsx` with: + +```tsx +'use client'; + +import { tokens } from '@ngaf/design-tokens'; +import { Container } from '../ui/Container'; +import { Section } from '../ui/Section'; +import { Eyebrow } from '../ui/Eyebrow'; +import { trackCtaClick } from '../../lib/analytics/client'; + +interface ProductionRow { + need: string; + description: string; + primitive: string; +} + +const PRODUCTION_ROWS: ProductionRow[] = [ + { + need: 'Durable threads', + description: 'Persist across reloads, resume, branch, replay.', + primitive: 'threadId signal + durable transports', + }, + { + need: 'Resumable interrupts', + description: 'Human-in-the-loop pause, resume token, retry, cancel.', + primitive: 'interrupt(), resume()', + }, + { + need: 'Tool calls as events', + description: 'Stream progress, structured args, surfaced errors.', + primitive: 'tool events on agent()', + }, + { + need: 'Streaming state as signals', + description: 'messages(), status(), error() — not promises.', + primitive: 'signal-native agent()', + }, + { + need: 'Generative UI on your design system', + description: 'Vercel json-render + Google A2UI rendered into your Angular components.', + primitive: '@ngaf/render', + }, + { + need: 'Recoverable errors', + description: 'Retry, reload, error boundaries, fallback content.', + primitive: 'error(), reload()', + }, + { + need: 'Backend portability', + description: 'LangGraph today; AG-UI / Mastra / CrewAI / your own tomorrow — same UI.', + primitive: 'runtime adapters behind one contract', + }, + { + need: 'Angular-native', + description: 'DI, signals, RxJS interop — no React rewrite.', + primitive: 'built on Angular primitives, not ported', + }, + { + need: 'Observability hooks', + description: 'Tracing seams; app telemetry off by default.', + primitive: 'event hooks, opt-in only', + }, + { + need: 'MIT + self-hosted', + description: 'Own the primitives long-term, no vendor lock-in.', + primitive: 'MIT-licensed, no runtime SaaS dependency', + }, +]; + +function CheckIcon() { + return ( + + ); +} + +export function Differentiator() { + return ( +
    + +
    + Why this exists +

    + Everything an Angular agent needs once the demo works. +

    +

    + A streaming chat tutorial takes an hour. Shipping a real agent — durable, interruptible, observable, on your design system — takes most teams six months. NGAF gives the Angular surface that the rest of the stack assumes you've already built. +

    +
    + +
      + {PRODUCTION_ROWS.map((row) => ( +
    • + +
      +
      + + {row.need} + + + {row.description} + +
      + + {row.primitive} + +
      +
    • + ))} +
    + +

    + Want help walking these on your codebase?{' '} + + trackCtaClick({ + surface: 'home', + destination_url: '/pilot-to-prod', + cta_id: 'home_why_pilot_to_prod', + cta_text: 'Pilot to Prod', + }) + } + style={{ color: tokens.colors.accent, textDecoration: 'none', fontWeight: 600 }} + > + Pilot to Prod → + +

    + + +
    +
    + ); +} +``` + +- [ ] **Step 2: Type-check the file** + +Run: `npx nx typecheck website 2>&1 | tail -20` +Expected: no errors referencing `Differentiator.tsx`. (Other unrelated typecheck output is fine; just confirm the file isn't in the error list.) + +If the project has no `typecheck` target, run: `npx tsc -p apps/website/tsconfig.json --noEmit 2>&1 | grep -i differentiator || echo "ok"` +Expected: `ok`. + +- [ ] **Step 3: Visual check in dev server** + +The website-dev preview server should already be running (port 3000). If not, start it via the harness preview tool. + +Use `preview_eval` to reload, then `preview_resize` to 1280×820, then `preview_screenshot`. Verify: +- Eyebrow "Why this exists" visible. +- Headline reads exactly "Everything an Angular agent needs once the demo works." +- 10 rows render, each with a check icon, bold need, muted description, mono primitive on the right. +- Footer line "Want help walking these on your codebase? Pilot to Prod →" appears below the list. + +- [ ] **Step 4: Commit** + +```bash +git add apps/website/src/components/landing/Differentiator.tsx +git commit -m "$(cat <<'EOF' +feat(website): rewrite "Why this exists" section as production-readiness checklist + +Replaces the 4-card differentiator grid with a 10-row checklist that dramatizes +the demos→production gap. Each row pairs a production need with the concrete +NGAF primitive that covers it. Footer hands off to /pilot-to-prod. + +See docs/superpowers/specs/2026-05-19-why-this-exists-section-design.md. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +--- + +## Task 2: Add Differentiator.spec.tsx + +**Files:** +- Create: `apps/website/src/components/landing/Differentiator.spec.tsx` + +- [ ] **Step 1: Confirm sibling test convention** + +Run: `ls apps/website/src/components/landing/*.spec.tsx 2>/dev/null | head -3` +Expected output may be empty (no landing specs yet) — that's fine; the test framework wiring still applies workspace-wide. + +Run: `grep -l "@testing-library/react" apps/website/package.json apps/website/vite.config.mts 2>/dev/null` +Expected: at least one match (RTL is already a dep). + +If RTL is *not* present, stop and add it before continuing: +```bash +npx nx run website:install --pkg=@testing-library/react --dev +``` +Expected: clean install. Then continue. + +- [ ] **Step 2: Write the failing test file** + +Create `apps/website/src/components/landing/Differentiator.spec.tsx`: + +```tsx +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { Differentiator } from './Differentiator'; + +vi.mock('../../lib/analytics/client', () => ({ + trackCtaClick: vi.fn(), + trackExternalLinkClick: vi.fn(), +})); + +import { trackCtaClick } from '../../lib/analytics/client'; + +const EXPECTED_NEEDS = [ + 'Durable threads', + 'Resumable interrupts', + 'Tool calls as events', + 'Streaming state as signals', + 'Generative UI on your design system', + 'Recoverable errors', + 'Backend portability', + 'Angular-native', + 'Observability hooks', + 'MIT + self-hosted', +]; + +describe('Differentiator', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders the section headline', () => { + render(); + expect( + screen.getByRole('heading', { + level: 2, + name: 'Everything an Angular agent needs once the demo works.', + }), + ).toBeTruthy(); + }); + + it('renders all 10 production-readiness rows', () => { + render(); + for (const need of EXPECTED_NEEDS) { + expect(screen.getByText(need)).toBeTruthy(); + } + }); + + it('renders the @ngaf/render primitive for the generative UI row', () => { + render(); + expect(screen.getByText('@ngaf/render')).toBeTruthy(); + }); + + it('links the footer CTA to /pilot-to-prod', () => { + render(); + const link = screen.getByRole('link', { name: /Pilot to Prod/ }); + expect(link.getAttribute('href')).toBe('/pilot-to-prod'); + }); + + it('fires the home_why_pilot_to_prod CTA event when the footer link is clicked', () => { + render(); + const link = screen.getByRole('link', { name: /Pilot to Prod/ }); + fireEvent.click(link); + expect(trackCtaClick).toHaveBeenCalledWith({ + surface: 'home', + destination_url: '/pilot-to-prod', + cta_id: 'home_why_pilot_to_prod', + cta_text: 'Pilot to Prod', + }); + }); +}); +``` + +- [ ] **Step 3: Run the test — verify it passes** + +Run: `npx nx test website -- --run Differentiator 2>&1 | tail -30` + +Expected: 5 passing tests under `Differentiator`. Exit code 0. + +If a test fails with "trackCtaClick is not a function" or similar — the mock path is wrong; double-check the import path matches what `Differentiator.tsx` uses (`../../lib/analytics/client`). + +If a test fails with "cannot find @testing-library/react" — RTL isn't installed; add it per Step 1's fallback. + +- [ ] **Step 4: Commit** + +```bash +git add apps/website/src/components/landing/Differentiator.spec.tsx +git commit -m "$(cat <<'EOF' +test(website): cover Differentiator rows, headline, and pilot-to-prod CTA + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +--- + +## Task 3: Verification pass + +**Files:** none (verification only) + +- [ ] **Step 1: Run the website test suite** + +Run: `npx nx test website 2>&1 | tail -20` +Expected: all tests pass. If a pre-existing failure surfaces unrelated to `Differentiator`, leave it — note it in the PR description but do not fix it as part of this plan. + +- [ ] **Step 2: Confirm the page still renders end-to-end** + +With the website-dev preview server running on port 3000, navigate to `/`. Use `preview_snapshot` and confirm: +- Section h2 reads "Everything an Angular agent needs once the demo works." +- 10 list items rendered. +- Footer link present and points to `/pilot-to-prod`. + +- [ ] **Step 3: Capture a screenshot for the PR** + +Resize preview to 1280×820, scroll to the section (it sits below the "Works with your agent stack" matrix), and capture `preview_screenshot`. Save as `/tmp/why-this-exists-after.png` for inclusion in the PR description. + +- [ ] **Step 4: Update auto-memory if anything novel surfaced** + +If any non-obvious quirk turned up (e.g., the project required a special vitest config flag, or `tokens.colors.accent` had to be swapped for another token), capture it as a feedback memory in `~/.claude/projects/-Users-blove-repos-angular-agent-framework/memory/`. + +Otherwise skip. + +--- + +## Self-review + +**Spec coverage:** +- Wedge / GTM alignment — captured in commit message + spec reference. ✓ +- Eyebrow, headline, subhead — Task 1 Step 1, literal strings match spec. ✓ +- 10 rows with need / description / primitive — Task 1 Step 1; Task 2 covers all 10 needs by name. ✓ +- Compact rows with check icon, no card grid — Task 1 Step 1 (`