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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Run the same discovery locally with an explicit date:
bun run history:news:pull -- --as-of 2026-08-20 --json-out /tmp/stripe-news.json --markdown-out /tmp/stripe-news.md
```

The manual [leadership appearance backfill](./.github/workflows/appearance-backfill.yml) searches one bounded calendar window at a time from 2009 onward and uploads a review artifact. It does not edit public data or open issues. The same window can be inspected locally:
The manual [leadership appearance backfill](./.github/workflows/appearance-backfill.yml) searches one bounded calendar window at a time from 2009 onward and uploads a review artifact. It does not edit public data or open issues. A reviewer can normalize a completed run into the public, nonindexable [appearance backfill queue](https://stripehistory.com/appearances/backfill); candidates remain separate from the reviewed appearance corpus until full source review. The same window can be inspected locally:

```sh
bun run history:news:pull -- --from 2020-01-01 --as-of 2020-12-31 --monitor exa-stripe-leadership-appearances --json-out /tmp/stripe-appearances-2020.json --markdown-out /tmp/stripe-appearances-2020.md
Expand Down
30 changes: 30 additions & 0 deletions app/appearances/backfill/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, test } from "bun:test";
import { renderToStaticMarkup } from "react-dom/server";

import AppearanceBackfillPage, { metadata } from "./page";

describe("leadership appearance backfill page", () => {
test("keeps unreviewed discovery results public but out of search results", () => {
expect(metadata).toMatchObject({
alternates: { canonical: "/appearances/backfill" },
robots: { follow: true, index: false },
title: "Leadership Appearance Backfill",
});
});

test("renders the complete normalized review queue with provenance", async () => {
const html = renderToStaticMarkup(await AppearanceBackfillPage());

expect(html).toContain('<h1 id="appearance-backfill-heading">Leadership Appearance Backfill</h1>');
expect(html).toContain("31 candidates");
expect(html).toContain("47 raw hits");
expect(html).toContain("not accepted historical records");
expect(html.match(/source review needed/gu)).toHaveLength(31);
expect(html).toContain('href="https://www.youtube.com/watch?v=YgYiF86h0yU"');
expect(html).toContain('href="https://www.youtube.com/watch?v=y_4emS6D4og"');
expect(html).toContain(
'href="https://github.com/hraness/stripe-history/actions/runs/32265726670"',
);
expect(html).not.toContain("OpenAI Co-founder Greg Brockman");
});
});
105 changes: 105 additions & 0 deletions app/appearances/backfill/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
loadAppearanceBackfill,
type AppearanceBackfill,
} from "@/lib/appearance-backfill";
import type { Metadata } from "next";
import Link from "next/link";

import { SiteFooter } from "../../site-footer";
import { SiteHeader } from "../../site-header";
import { site, socialMetadata } from "../../site";

const title = "Leadership Appearance Backfill";
const description =
"A public review queue of historical Stripe founder and executive podcast, interview, talk, and testimony candidates discovered through bounded Exa search.";

export const dynamic = "force-static";

export const metadata: Metadata = {
title,
description,
alternates: { canonical: "/appearances/backfill" },
robots: { follow: true, index: false },
...socialMetadata(`${title} | ${site.domain}`, description, "/appearances/backfill"),
};

export default async function AppearanceBackfillPage() {
const backfill = await loadAppearanceBackfill();
const candidatesByYear = new Map<
string,
AppearanceBackfill["candidates"][number][]
>();
for (const candidate of backfill.candidates) {
const year = candidate.published_at.slice(0, 4);
const candidates = candidatesByYear.get(year) ?? [];
candidates.push(candidate);
candidatesByYear.set(year, candidates);
}

return (
<main className="plain-page stripe-history-main stripe-history-appearances-page" id="main-content">
<SiteHeader appearancesSelected />
<nav aria-label="Breadcrumb" className="stripe-history-breadcrumbs">
<Link href="/">history</Link>
<span aria-hidden="true"> / </span>
<Link href="/appearances">appearances</Link>
<span aria-hidden="true"> / </span>
<span>backfill</span>
</nav>
<section aria-labelledby="appearance-backfill-heading" className="stripe-history-section">
<div className="stripe-history-section-heading">
<h1 id="appearance-backfill-heading">{title}</h1>
<span>{backfill.candidates.length} candidates</span>
</div>
<div className="stripe-history-backfill-intro">
<p>{description}</p>
<p>
These links are discovery results, not accepted historical records.
Each one still needs canonical deduplication, complete source capture,
role verification, and transcript-grounded editorial review before it
can join the <Link href="/appearances">reviewed appearances</Link>.
</p>
<p>
The <a href={backfill.workflow_run}>successful backfill run</a> searched
{" "}{backfill.review_window.from.slice(0, 4)} through{" "}
{backfill.review_window.through.slice(0, 4)} and returned{" "}
{backfill.counts.raw_hits} raw hits. Review collapsed{" "}
{backfill.counts.duplicate_variants} duplicate source variants, matched{" "}
{backfill.counts.already_reviewed_variants} variants to existing records,
and excluded {backfill.counts.excluded_hits} unrelated hits.
</p>
</div>
<div className="stripe-history-backfill-years">
{[...candidatesByYear.entries()].map(([year, candidates]) => (
<section aria-labelledby={`backfill-${year}`} key={year}>
<div className="stripe-history-section-heading">
<h2 id={`backfill-${year}`}>{year}</h2>
<span>{candidates.length}</span>
</div>
<ol className="stripe-history-backfill-list">
{candidates.map((candidate) => (
<li key={candidate.url}>
<article>
<p className="stripe-history-appearance-kicker">
<time dateTime={candidate.published_at}>{candidate.published_at}</time>
<span>source review needed</span>
</p>
<h3><a href={candidate.url}>{candidate.title}</a></h3>
<p className="stripe-history-appearance-participants">
{candidate.participants.join(" · ")}
</p>
<p className="stripe-history-backfill-source">
{new URL(candidate.url).hostname}
</p>
</article>
</li>
))}
</ol>
</section>
))}
</div>
</section>
<SiteFooter />
</main>
);
}
2 changes: 2 additions & 0 deletions app/appearances/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ describe("Stripe leadership appearances", () => {
expect(html).toContain('"@type":"CollectionPage"');
expect(html).toContain('"@type":"PodcastEpisode"');
expect(html).toContain('"@type":"VideoObject"');
expect(html).toContain('href="/appearances/backfill"');
expect(html).toContain("Review 31 historical appearance candidates");
});
});
12 changes: 11 additions & 1 deletion app/appearances/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { loadHistory } from "@/lib/content";
import { loadAppearanceBackfill } from "@/lib/appearance-backfill";
import { JsonLdScript } from "@hraness/web-discovery/json-ld";
import type { Metadata } from "next";
import Link from "next/link";
Expand Down Expand Up @@ -29,7 +30,10 @@ function durationLabel(seconds: number | undefined): string | null {
}

export default async function AppearancesPage() {
const history = await loadHistory();
const [history, backfill] = await Promise.all([
loadHistory(),
loadAppearanceBackfill(),
]);
const sourceById = new Map(history.sources.map((source) => [source.id, source]));

return (
Expand All @@ -56,6 +60,12 @@ export default async function AppearancesPage() {
<span>{history.appearances.length} reviewed</span>
</div>
<p className="stripe-history-appearances-intro">{description}</p>
<p className="stripe-history-appearances-intro">
<Link href="/appearances/backfill">
Review {backfill.candidates.length} historical appearance candidates
</Link>{" "}
from the public 2009–2026 leadership backfill.
</p>
<ol className="stripe-history-appearance-list">
{history.appearances.map((appearance) => {
const sources = appearance.source_ids.flatMap((sourceId) => {
Expand Down
3 changes: 3 additions & 0 deletions app/data/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ describe("Stripe company history dataset", () => {
expect(html).toContain('href="/research/sources.yml"');
expect(html).toContain('href="/research/valuations.yml"');
expect(html).toContain('href="/research/appearances.yml"');
expect(html).toContain('href="/appearances/backfill"');
expect(html).toContain('href="/research/appearance-backfill.yml"');
expect(html).toContain("31 candidates");
expect(html).toContain('href="/research/collections.yml"');
expect(html).toContain('href="/research/runs.yml"');
expect(html).toContain(`${history.sources.length} canonical sources`);
Expand Down
11 changes: 10 additions & 1 deletion app/data/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { loadHistory } from "@/lib/content";
import { loadAppearanceBackfill } from "@/lib/appearance-backfill";
import { JsonLdScript } from "@hraness/web-discovery/json-ld";
import type { Metadata } from "next";
import Link from "next/link";
Expand All @@ -23,7 +24,10 @@ export const metadata: Metadata = {
};

export default async function DataPage() {
const history = await loadHistory();
const [history, appearanceBackfill] = await Promise.all([
loadHistory(),
loadAppearanceBackfill(),
]);
const countByCategory = new Map(
history.categories.map(({ id }) => [
id,
Expand Down Expand Up @@ -166,6 +170,11 @@ export default async function DataPage() {
<a href="/research/appearances.yml">YAML</a> ·{" "}
{history.appearances.length} appearances
</li>
<li>
<Link href="/appearances/backfill">appearance backfill queue</Link> ·{" "}
<a href="/research/appearance-backfill.yml">YAML</a> ·{" "}
{appearanceBackfill.candidates.length} candidates
</li>
<li>
<a href="/research/collections.yml">research collections YAML</a>
</li>
Expand Down
53 changes: 53 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,59 @@
text-align: end;
}

.stripe-history-backfill-intro {
color: var(--plain-muted);
display: grid;
gap: 0.7rem;
margin-bottom: 2rem;
max-width: 42rem;
}

.stripe-history-backfill-intro p,
.stripe-history-backfill-list h3,
.stripe-history-backfill-list p {
margin: 0;
}

.stripe-history-backfill-years {
display: grid;
gap: 2rem;
}

.stripe-history-backfill-years > section > .stripe-history-section-heading {
border-top: 2px solid var(--plain-foreground);
margin-bottom: 0;
padding-top: 0.5rem;
}

.stripe-history-backfill-list {
list-style: none;
margin: 0;
padding: 0;
}

.stripe-history-backfill-list > li {
border-top: 1px solid var(--plain-line);
}

.stripe-history-backfill-list article {
padding-block: 0.85rem 1rem;
}

.stripe-history-backfill-list h3 {
font-family: var(--font-heading);
font-size: var(--text-label);
line-height: 1.35;
margin-top: 0.25rem;
}

.stripe-history-backfill-list .stripe-history-appearance-participants,
.stripe-history-backfill-source {
color: var(--plain-muted);
font-size: var(--text-caption);
margin-top: 0.3rem;
}

.stripe-history-state button {
background: var(--plain-foreground);
border: 1px solid var(--plain-foreground);
Expand Down
43 changes: 43 additions & 0 deletions lib/appearance-backfill.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, test } from "bun:test";
import { readFile } from "node:fs/promises";
import { join } from "node:path";

import { parse } from "yaml";

import {
AppearanceBackfillFileSchema,
loadAppearanceBackfill,
} from "./appearance-backfill";

describe("leadership appearance backfill", () => {
test("accounts for every raw discovery hit without promoting candidates", async () => {
const backfill = await loadAppearanceBackfill();

expect(backfill.counts).toEqual({
already_reviewed_variants: 9,
duplicate_variants: 5,
excluded_hits: 2,
raw_hits: 47,
});
expect(backfill.candidates).toHaveLength(31);
expect(backfill.candidates.every(({ review_status: status }) =>
status === "source-review-needed")).toBe(true);
expect(backfill.candidates[0]?.published_at).toBe("2026-05-21");
expect(backfill.candidates.at(-1)?.published_at).toBe("2013-08-13");
});

test("rejects incomplete hit accounting", async () => {
const value = parse(await readFile(join(
process.cwd(),
"public",
"research",
"appearance-backfill.yml",
), "utf8")) as Record<string, unknown>;
const counts = value.counts as Record<string, unknown>;
counts.raw_hits = 48;

expect(() => AppearanceBackfillFileSchema.parse(value)).toThrow(
"Backfill accounting covers 47 of 48 raw hits",
);
});
});
Loading