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
2 changes: 1 addition & 1 deletion .agent/architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"dist/**",
"node_modules/**"
],
"digest": "4137d8bcbd0f88346d9213830c19603fc0a0d23ecbc20258252ea082073778f7"
"digest": "ac0aa02b8f1bf8aef872b5fd07ca988659b6269ce14e60ceaa4825857d3cf7e4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record the new route in the architecture contract

Refreshing only this digest leaves both architecture inventories stale: .agent/architecture.json still has no interface entry for /past-the-pilot/, and docs/architecture.html still omits it from the route map. Anyone using the repository's synchronized contract and human map will therefore be told that the newly public purchase boundary does not exist; update both inventories and then regenerate their hashes.

AGENTS.md reference: AGENTS.md:L78-L80

Useful? React with 👍 / 👎.

},
"components": [
{
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A standalone architecture map for the PersonalSite repository.">
<meta name="architecture-contract-sha256" content="65f6a5f7d043666fb35093dab8993a4c8f16832dabdac313cb9107b6253a6cde">
<meta name="architecture-contract-sha256" content="19780001a1b30e2bb18efc2ada106ce2cb28b992222cbad9e6101ce9bc3f2cfa">
<title>PersonalSite architecture map</title>
<style>
:root {
Expand Down
34 changes: 34 additions & 0 deletions src/data/career.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export interface Role {
readonly id: string;
readonly company: string;
readonly summary?: string;
}

export const currentRole: Role = {
id: 'salesforce',
company: 'Salesforce',
summary: 'director of product, emerging technology. 2025 to present. enterprise ai adoption, 0 to 1: how large organizations actually absorb ai, and what makes it stick after the first rollout.',
};

export const pastRoles: readonly Role[] = [
{
id: 'siriusxm',
company: 'SiriusXM',
summary: 'cross-platform listening growth for 34M subscribers across mobile, web, and in-car. 18% more daily sessions, 12% less voluntary churn in the first 90 days, and 3x the shipping cadence.',
},
{
id: 'disney-plus',
company: 'Disney/ESPN',
summary: 'global launch operations: the orchestration layer that let teams localize and certify content, payments, and ux strings. 60+ markets live with feature parity on day one, 35% faster localization.',
},
{ id: 'hyperscience', company: 'Hyperscience' },
{
id: 'ea-sports',
company: 'EA Sports',
summary: 'built the EA Sports companion app from zero: hired a 40-person team, ran two betas at 90% csat and 86% weekly retention, and handed off what became the EA Sports App.',
},
];

export const pastEmployers: string = new Intl.ListFormat('en', { type: 'conjunction' }).format(
pastRoles.map((role) => role.company),
);
4 changes: 2 additions & 2 deletions src/data/siteMetadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ describe('siteMetadata', () => {
expect(siteMetadata.title).toBe('Adrian Lumley');
expect(siteMetadata.siteName).toBe('Adrian Lumley');
expect(siteMetadata.jobTitle).toBe('Director of Product Management');
expect(siteMetadata.personDescription).toBe('Director of product management at Salesforce on the emerging technology team, working on enterprise AI adoption. Previously SiriusXM, Disney+, and EA.');
// the structured data only claims what the visible pages say
expect(siteMetadata.description).toBe('director of product at salesforce. previously siriusxm, disney/espn, hyperscience, and ea sports. building ai systems hands-on in nyc.');
expect(siteMetadata.personDescription).toBe('Director of product management at Salesforce on the emerging technology team, working on enterprise AI adoption. Previously SiriusXM, Disney/ESPN, Hyperscience, and EA Sports.');
expect(siteMetadata.personDescription).not.toMatch(/diligence|Trust Layer/);
expect(siteMetadata.alumniOf).toEqual([
"St. John's University",
Expand Down
6 changes: 4 additions & 2 deletions src/data/siteMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { pastEmployers } from './career';

export const siteMetadata = {
siteName: 'Adrian Lumley',
title: 'Adrian Lumley',
description: 'director of product at salesforce. previously siriusxm, disney+, and ea. building ai systems hands-on in nyc.',
description: `director of product at salesforce. previously ${pastEmployers.toLowerCase()}. building ai systems hands-on in nyc.`,
author: 'Adrian Lumley',
url: 'https://adrianlumley.co',
image: '/images/adrian-lumley.jpg',
jobTitle: 'Director of Product Management',
personDescription: 'Director of product management at Salesforce on the emerging technology team, working on enterprise AI adoption. Previously SiriusXM, Disney+, and EA.',
personDescription: `Director of product management at Salesforce on the emerging technology team, working on enterprise AI adoption. Previously ${pastEmployers}.`,
alumniOf: [
"St. John's University",
'Quantic School of Business and Technology',
Expand Down
30 changes: 4 additions & 26 deletions src/pages/about.astro
Original file line number Diff line number Diff line change
@@ -1,36 +1,14 @@
---
import Base from '../layouts/Base.astro';
import PageIntro from '../components/PageIntro.astro';
import { currentRole, pastEmployers, pastRoles } from '../data/career';

const goodreads = 'https://www.goodreads.com/user/show/149439647-adrian-lumley';

// the work record: one row per role, the same key/value shape as the
// homepage ledger. /work/ redirects here.
const work = [
{
id: 'salesforce',
company: 'Salesforce',
summary: 'director of product, emerging technology. 2025 to present. enterprise ai adoption, 0 to 1: how large organizations actually absorb ai, and what makes it stick after the first rollout.',
},
{
id: 'siriusxm',
company: 'SiriusXM',
summary: 'cross-platform listening growth for 34M subscribers across mobile, web, and in-car. 18% more daily sessions, 12% less voluntary churn in the first 90 days, and 3x the shipping cadence.',
},
{
id: 'disney-plus',
company: 'Disney+',
summary: 'global launch operations: the orchestration layer that let teams localize and certify content, payments, and ux strings. 60+ markets live with feature parity on day one, 35% faster localization.',
},
{
id: 'ea-sports',
company: 'EA',
summary: 'built the EA Sports companion app from zero: hired a 40-person team, ran two betas at 90% csat and 86% weekly retention, and handed off what became the EA Sports App.',
},
];
const work = [currentRole, ...pastRoles];
---

<Base title="About" description="about adrian lumley: product leader, fourteen years in, director of product at salesforce on emerging technology. previously siriusxm, disney+, and ea.">
<Base title="About" description={`about adrian lumley: product leader, fourteen years in, director of product at salesforce on emerging technology. previously ${pastEmployers.toLowerCase()}.`}>
<PageIntro title="about" standfirst="product leader, builder, and new yorker working on enterprise ai adoption." />

<div class="content-stack">
Expand All @@ -44,7 +22,7 @@ const work = [
{work.map((role) => (
<div class="row" id={role.id}>
<dt class="row__key">{role.company}</dt>
<dd class="row__value">{role.summary}</dd>
<dd class="row__value">{role.summary ?? ''}</dd>
</div>
))}
</dl>
Expand Down
5 changes: 3 additions & 2 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import Base from '../layouts/Base.astro';
import { currentRole, pastRoles } from '../data/career';
import { getPublishedPosts, postHref } from '../lib/content';
import { formatMonthYear } from '../lib/format';

Expand Down Expand Up @@ -55,9 +56,9 @@ const footerLinks = [

<dl class="record__ledger">
<dt class="record__label">currently</dt>
<dd>Salesforce.</dd>
<dd>{currentRole.company}.</dd>
<dt class="record__label">past</dt>
<dd>SiriusXM, Disney+, EA.</dd>
<dd>{pastRoles.map((role) => role.company).join(', ')}.</dd>
</dl>

<section class="record__section" aria-labelledby="writing-h">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
---
// Held off the public site until outside work is approved.
// Not a route: Astro only publishes files under src/pages.
// To restore, move this file to src/pages/past-the-pilot.astro and point the
// imports at ../layouts/Base.astro and ../components/PageIntro.astro.
import Base from '../../src/layouts/Base.astro';
import PageIntro from '../../src/components/PageIntro.astro';
import Base from '../layouts/Base.astro';
import PageIntro from '../components/PageIntro.astro';
---

<Base title="Past the Pilot" description="Async written audit of one AI feature that is live but not earning: kill, fix, or ship-next. $1,500. No discovery call.">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the purchase page to the canonical sitemap

This creates the public purchase route, but src/lib/sitemap.ts builds the sitemap from the hard-coded canonicalPublicAstroPaths allowlist, which still omits /past-the-pilot/. A repo-wide search also finds no internal link to this route, so crawlers relying on the site's links or sitemap cannot discover the newly published offer; add the purchase route to that allowlist while keeping the post-payment thanks page excluded.

Useful? React with 👍 / 👎.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
---
// Held off the public site until outside work is approved.
// Not a route: Astro only publishes files under src/pages.
// To restore, move this file to src/pages/past-the-pilot/thanks.astro and point
// the imports at ../../layouts/Base.astro and ../../components/PageIntro.astro.
import Base from '../../src/layouts/Base.astro';
import PageIntro from '../../src/components/PageIntro.astro';
import Base from '../../layouts/Base.astro';
import PageIntro from '../../components/PageIntro.astro';
---

<Base title="you're in" description="Past the Pilot payment received. Send your materials for the async audit.">
Expand Down
2 changes: 1 addition & 1 deletion tests/base-layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ test.describe('base layout seo', () => {
url: 'https://adrianlumley.co',
image: 'https://adrianlumley.co/images/adrian-lumley.jpg',
jobTitle: 'Director of Product Management',
description: 'Director of product management at Salesforce on the emerging technology team, working on enterprise AI adoption. Previously SiriusXM, Disney+, and EA.',
description: 'Director of product management at Salesforce on the emerging technology team, working on enterprise AI adoption. Previously SiriusXM, Disney/ESPN, Hyperscience, and EA Sports.',
knowsAbout: [
'Product management',
'Enterprise AI adoption',
Expand Down
3 changes: 1 addition & 2 deletions tests/e2e/homepage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,9 @@ test('homepage ledger, archive, lab rows, and footer are reconciled', async ({ p
await page.goto('/');
const main = page.locator('main');

// ledger
const ledger = page.locator('.record__ledger');
await expect(ledger.locator('dt')).toHaveText(['currently', 'past']);
await expect(ledger.locator('dd')).toHaveText(['Salesforce.', 'SiriusXM, Disney+, EA.']);
await expect(ledger.locator('dd')).toHaveText(['Salesforce.', 'SiriusXM, Disney/ESPN, Hyperscience, EA Sports.']);
await expect(main).not.toContainText('director of product');
await expect(main).not.toContainText('diligence');
await expect(main).not.toContainText('case studies');
Expand Down
16 changes: 16 additions & 0 deletions tests/e2e/past-the-pilot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test';

test('past the pilot is public and checks out through the existing stripe link', async ({ page }) => {
const response = await page.goto('/past-the-pilot/');
expect(response?.status()).toBe(200);
await expect(page.getByRole('link', { name: 'buy past the pilot' })).toHaveAttribute(
'href',
'https://buy.stripe.com/28E3cx3VG6TwaMl10o6Zy01',
);
});

test('past the pilot thanks page is public and links to contact', async ({ page }) => {
const response = await page.goto('/past-the-pilot/thanks/');
expect(response?.status()).toBe(200);
await expect(page.getByRole('link', { name: 'send materials via contact' })).toHaveAttribute('href', '/contact/');
});
2 changes: 1 addition & 1 deletion tests/e2e/site-basics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test('nav: header shows the home wordmark + canonical primary tabs', async ({ pa
});

test('scrubbed pages are gone, not redirected', async ({ page }) => {
for (const path of ['/360/', '/joytap-privacy/', '/writing/joytap-one-sprint/', '/lab/iron-log/', '/lab/night-train/', '/signal-room/', '/signal-room/night-shift/', '/past-the-pilot/', '/past-the-pilot/thanks/']) {
for (const path of ['/360/', '/joytap-privacy/', '/writing/joytap-one-sprint/', '/lab/iron-log/', '/lab/night-train/', '/signal-room/', '/signal-room/night-shift/']) {
const response = await page.goto(path);
expect(response?.status(), path).toBe(404);
}
Expand Down
15 changes: 12 additions & 3 deletions tests/work.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@ test('about page carries the work record, one row per role', async ({ page }) =>
await page.goto('/about/');
const work = page.locator('#work');
await expect(work.getByRole('heading', { name: 'work' })).toBeVisible();
for (const company of ['Salesforce', 'SiriusXM', 'Disney+', 'EA']) {
await expect(work.getByText(company, { exact: true })).toBeVisible();
}
await expect(work.locator('dt')).toHaveText([
'Salesforce',
'SiriusXM',
'Disney/ESPN',
'Hyperscience',
'EA Sports',
]);
await expect(page.locator('#hyperscience')).toHaveText('Hyperscience');
await expect(page.locator('meta[name="description"]')).toHaveAttribute(
'content',
'about adrian lumley: product leader, fourteen years in, director of product at salesforce on emerging technology. previously siriusxm, disney/espn, hyperscience, and ea sports.',
);
await expect(work.getByText(/18% more daily sessions/)).toBeVisible();
await expect(work.getByText(/what makes it stick after the first rollout/)).toBeVisible();
await expect(page.getByRole('heading', { name: 'open to' })).toHaveCount(0);
Expand Down
Loading