Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
86a10c4
docs(specs): homepage rebuild design — category hero, live take-over …
blove Sep 2, 2026
a367f47
docs(specs): homepage rebuild — pin /hero route design to verified ad…
blove Sep 3, 2026
3677c71
docs(plans): homepage rebuild — hero demo route and website implement…
blove Sep 3, 2026
9f74515
feat(examples/chat): hero recording types and validator
blove Sep 3, 2026
2333187
feat(examples/chat): HeroReplayTransport plays recorded runs with cla…
blove Sep 3, 2026
898284b
feat(examples/chat): HeroRecordingTransport captures live runs for th…
blove Sep 3, 2026
5e4bc06
fix(examples/chat): avoid require-yield lint error in HeroRecordingTr…
blove Sep 3, 2026
becef74
feat(examples/chat): HeroScriptRunner drives the walkthrough through …
blove Sep 3, 2026
e8827d6
feat(examples/chat): hero postMessage bridge with parent-origin allow…
blove Sep 3, 2026
3a9d6a8
feat(examples/chat): hero cursor component
blove Sep 3, 2026
5d0311e
refactor(examples/chat): HeroReplayTransport needs no Injectable deco…
blove Sep 3, 2026
a3d882c
fix(examples/chat): avoid empty-function lint error in hero-script spec
blove Sep 3, 2026
a2b3ec8
feat(examples/chat): register lazy /hero route and hero agent refs
blove Sep 3, 2026
8deffc4
fix(examples/chat): hero transports — retry failed fixture loads, reg…
blove Sep 3, 2026
032664d
feat(examples/chat): HeroMode route — replay agent, scripted cursor, …
blove Sep 3, 2026
db7c619
fix(examples/chat): hero script runner generation token, timeouts and…
blove Sep 3, 2026
d71d407
feat(examples/chat): record and commit the hero walkthrough fixture
blove Sep 3, 2026
1ba814e
fix(examples/chat): hero mode — takeover gating, visibility sources, …
blove Sep 3, 2026
9a0640a
test(examples/chat): e2e for the hero walkthrough and takeover
blove Sep 3, 2026
d4bc4a8
feat(website): hero walkthrough poster captured from /hero
blove Sep 3, 2026
511f8ee
fix(examples/chat): hero mode — honest initial frame state, guarded r…
blove Sep 3, 2026
03e4ddd
fix(examples/chat): hero bridge learns the parent origin from the fir…
blove Sep 3, 2026
67df584
fix(examples/chat): type the hero recording validator callbacks for s…
blove Sep 3, 2026
6309055
Merge branch 'main' into blove/homepage-rebuild-spec
blove Sep 3, 2026
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,844 changes: 1,844 additions & 0 deletions docs/superpowers/plans/2026-09-02-hero-demo-route.md

Large diffs are not rendered by default.

2,604 changes: 2,604 additions & 0 deletions docs/superpowers/plans/2026-09-02-homepage-rebuild.md

Large diffs are not rendered by default.

254 changes: 254 additions & 0 deletions docs/superpowers/specs/2026-09-02-homepage-rebuild-design.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions examples/chat/angular/e2e/fixtures/interrupt-approval.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"fixtures": [
{
"match": {
"userMessage": "I want to clean up old database backups older than 90 days. Walk me through what you would delete, and call request_approval before doing anything destructive so I can review your plan.",
"hasToolResult": true
},
"response": {
"content": "Approved. Here is the cleanup I would run:\n\n1. **Inventory** every backup location (S3, GCS, Azure, RDS and EBS snapshots, and the local backup table).\n2. **Dry run** a listing of everything older than 90 days so you can scan it before anything is touched.\n3. **Delete** the matched objects and snapshots, moving anything tagged `retain` to the archive bucket instead, and write an audit record for each deletion.\n\nNothing has been deleted yet \u2014 the dry-run listing comes first."
}
},
{
"match": {
"userMessage": "I want to clean up old database backups older than 90 days. Walk me through what you would delete, and call request_approval before doing anything destructive so I can review your plan."
Expand Down
47 changes: 47 additions & 0 deletions examples/chat/angular/e2e/hero.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
import { test, expect } from '@playwright/test';
import { attachBrowserHygiene } from './test-helpers';

test.describe('hero walkthrough', () => {
test('replays to the interrupt, takes over to live, and can replay again', async ({ page }) => {
const hygiene = attachBrowserHygiene(page);
await page.goto('/hero');
const pill = page.locator('[data-hero-pill]');
await expect(pill).toContainText(/recorded LangGraph run/i);
// The script types and sends; the replayed run pauses on the interrupt.
await expect(page.locator('hero-cursor')).toHaveAttribute('data-visible', 'true', {
timeout: 15_000,
});
await expect(page.locator('chat-interrupt-panel')).toBeAttached({ timeout: 60_000 });
// Takeover via the pill.
await page.getByRole('button', { name: /take control/i }).click();
await expect(pill).toContainText(/Live · LangGraph/);
await expect(page.locator('[data-hero-banner]')).toContainText(/walkthrough was a recording/i);
await expect(page.locator('hero-cursor')).toHaveAttribute('data-visible', 'false');
// Back to replay.
await page.getByRole('button', { name: /replay walkthrough/i }).click();
await expect(pill).toContainText(/recorded LangGraph run/i);
expect(hygiene.consoleErrors).toEqual([]);
});

test('the full walkthrough reaches the generated UI without taking control of itself', async ({
page,
}) => {
const hygiene = attachBrowserHygiene(page);
await page.goto('/hero');
const pill = page.locator('[data-hero-pill]');
await expect(page.locator('chat-interrupt-panel')).toBeAttached({ timeout: 60_000 });
// Accept is pressed by the script; the A2UI surface from run 3 renders.
await expect(page.locator('a2ui-surface').first()).toBeAttached({ timeout: 90_000 });
await expect(pill).toContainText(/recorded LangGraph run/i);
expect(hygiene.consoleErrors).toEqual([]);
});

test('focusing the composer takes over', async ({ page }) => {
const hygiene = attachBrowserHygiene(page);
await page.goto('/hero');
await page.locator('[data-hero-surface] textarea').focus();
await expect(page.locator('[data-hero-pill]')).toContainText(/Live · LangGraph/);
expect(hygiene.consoleErrors).toEqual([]);
});
});
45 changes: 45 additions & 0 deletions examples/chat/angular/e2e/record-hero-fixture.record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
/**
* NOT a test. Drives /hero?record=1 (the scripted walkthrough runs against the
* live agent wrapped in HeroRecordingTransport) and writes the captured runs
* to public/hero-replay.json. Run through record-hero.config.ts.
*/
import { test, expect } from '@playwright/test';
import { writeFileSync } from 'node:fs';
import { resolve } from 'node:path';

interface RecordingShape {
version: 1;
recordedAt: string;
runs: { label: string; events: { tMs: number; event: unknown }[] }[];
}
/* eslint-disable @typescript-eslint/no-explicit-any */
const readRecording = () => (window as any).__heroRecording as RecordingShape | undefined;

const OUT = resolve(__dirname, '../public/hero-replay.json');

test('record hero walkthrough fixture', async ({ page }) => {
page.on('console', (m) => {
if (m.type() === 'error') console.log('[browser]', m.text());
});
await page.goto('/hero?record=1');
await expect
.poll(async () => page.evaluate(() => (window as any).__heroRecording?.runs.length ?? 0), {
timeout: 200_000,
})
.toBe(3);
// Let the last run drain: wait until the genui run has stopped growing for 3s.
let last = -1;
for (let i = 0; i < 40; i++) {
const n = await page.evaluate(() => (window as any).__heroRecording?.runs[2]?.events.length ?? 0);
if (n > 0 && n === last) break;
last = n;
await page.waitForTimeout(3000);
}
const rec = (await page.evaluate(readRecording)) as RecordingShape | undefined;
expect(rec?.runs.map((r) => r.label)).toEqual(['prompt', 'resume', 'genui']);
expect(JSON.stringify(rec?.runs[0].events)).toMatch(/approval_request/);
expect(JSON.stringify(rec?.runs[2].events)).toMatch(/a2ui/i);
writeFileSync(OUT, JSON.stringify(rec, null, 2) + '\n');
console.log(`wrote ${OUT}`);
});
30 changes: 30 additions & 0 deletions examples/chat/angular/e2e/record-hero-poster.record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
/**
* NOT a test. Captures a frame of the hero walkthrough as the website's
* server-rendered poster (1200x720, webp). Run through record-hero.config.ts:
* npx playwright test --config examples/chat/angular/e2e/record-hero.config.ts record-hero-poster
*
* The beat is the FIRST STREAMED REPLY: the approval interrupt has just been
* accepted by the scripted cursor and the answer is on screen. The earlier
* "typing the first prompt" frame is mostly empty canvas; this one shows the
* user turn, the tool call, the rendered answer and the cursor heading back to
* the composer — it reads as a product, not as a blank chat box.
*/
import { test } from '@playwright/test';
import { resolve } from 'node:path';
import sharp from 'sharp';

const OUT = resolve(__dirname, '../../../../apps/website/public/screenshots/hero-walkthrough-poster.webp');

test('capture hero poster', async ({ page }) => {
await page.goto('/hero');
// The scripted cursor types prompt 1, sends, and the replay pauses on the
// approval interrupt; the script then presses Accept.
const interruptPanel = page.locator('chat-interrupt-panel');
await interruptPanel.waitFor({ timeout: 60_000 });
await interruptPanel.waitFor({ state: 'detached', timeout: 60_000 });
await page.waitForTimeout(1500);
const png = await page.screenshot({ type: 'png', fullPage: false });
await sharp(png).webp({ quality: 82 }).toFile(OUT);
console.log(`wrote ${OUT}`);
});
29 changes: 29 additions & 0 deletions examples/chat/angular/e2e/record-hero.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
/**
* Playwright config for recording the hero walkthrough fixture. Mirrors
* `record-demo.config.ts` — same aimock-backed global setup — but captures no
* video: the artifact is `public/hero-replay.json`, not a clip.
*
* `testMatch` picks up only the hero record script, so recording never runs in
* CI and the e2e suite never records.
*
* npx playwright test --config examples/chat/angular/e2e/record-hero.config.ts record-hero-fixture
*/
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: '.',
testMatch: '**/record-hero-*.record.ts',
fullyParallel: false,
workers: 1,
retries: 0,
reporter: 'list',
timeout: 240_000,
use: {
baseURL: 'http://localhost:4200',
viewport: { width: 1200, height: 720 },
},
globalSetup: './global-setup.ts',
globalTeardown: './global-teardown.ts',
outputDir: './.record-output',
});
Loading
Loading