Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
}
4 changes: 2 additions & 2 deletions apps/website/e2e/website.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/website/src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ export default async function OpenGraphImage() {
>
<div style={{ display: 'flex', gap: 12 }}>
{POSITIONING_PROOF_POINTS.slice(0, 3).map((proofPoint, index) => (
<PillBadge key={proofPoint} tone={index === 0 ? 'accent' : 'neutral'}>
{proofPoint}
<PillBadge key={proofPoint.label} tone={index === 0 ? 'accent' : 'neutral'}>
{proofPoint.label}
</PillBadge>
))}
</div>
Expand Down
101 changes: 101 additions & 0 deletions apps/website/src/components/landing/Differentiator.spec.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => <div>{children}</div>,
}));
vi.mock('../ui/Section', () => ({
Section: ({ children }: { children: React.ReactNode }) => <section>{children}</section>,
}));
vi.mock('../ui/Eyebrow', () => ({
Eyebrow: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
}));
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(<Differentiator />);
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(<Differentiator />);
for (const need of EXPECTED_NEEDS) {
expect(screen.getByText(need)).toBeTruthy();
}
});

it('renders the @ngaf/render primitive for the generative UI row', () => {
render(<Differentiator />);
expect(screen.getByText('@ngaf/render')).toBeTruthy();
});

it('links the footer CTA to /pilot-to-prod', () => {
render(<Differentiator />);
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(<Differentiator />);
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',
});
});
});
Loading
Loading