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
3 changes: 2 additions & 1 deletion app/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

- `page.tsx` – the canonical unified Stripe history timeline.
- `history/` – category pages, the appearances projection, annual-volume and valuation pages, and shared timeline rendering.
- `x-markdown/` – the Node handler that renders the same public URLs as Markdown.
- `data/` – the crawlable history and research dataset index.
- `about/`, `contact/`, and `privacy/` – sourcing, review, independence, corrections, public contact channels, and privacy.
- `llms.txt/` – the agent index with when-to-use guidance.
- Root `proxy.ts` – Accept negotiation that serves Markdown for the same public URLs.
- Root `proxy.ts` – Accept negotiation that rewrites Markdown requests to the Node corpus handler. Do not read YAML in the proxy.
- `site.ts`, `site-copy.ts`, `site-header.tsx`, and `site-footer.tsx` – canonical identity, shared editorial copy, and shared page chrome.
- `analytics.ts`, `posthog.ts`, and `posthog-analytics.tsx` – the finite public-route analytics contract, strict PostHog boundary, and client provider.
- `layout.tsx`, `globals.css`, and `support/` – the document, appearance, structured-data, and portable styling boundaries.
Expand Down
9 changes: 9 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,15 @@
text-decoration: none;
}

.history-page-title {
font-family: var(--font-heading);
font-size: var(--text-title);
font-weight: 700;
line-height: 1.15;
margin: 0 0 1rem;
max-width: 42rem;
}

.history-volume-intro {
color: var(--plain-muted);
margin: 0 0 2.5rem;
Expand Down
4 changes: 4 additions & 0 deletions app/history/[category]/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ describe("stripedex.com category history", () => {
expect(html.indexOf('data-filter-id="all"')).toBeLessThan(
html.indexOf('data-filter-id="acquisitions"'),
);
expect(html).not.toContain("Loading Stripe company history");
expect(html).toContain("Stripe reportedly discusses acquiring OpenRouter");
expect(html).not.toContain(
'href="/history/acquisitions/openrouter-acquisition-talks-reported"',
);
expect(html).toContain('id="stripedex-history-category-structured-data"');
expect(html).not.toContain('class="stripedex-selector"');
expect(html).toContain('class="stripedex-header"');
Expand Down
88 changes: 88 additions & 0 deletions app/history/history-event-article.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { CategorizedHistoryEvent } from "@/lib/content";
import { historyCategoryPath } from "@/lib/history-urls";
import Link from "next/link";

import { HistoryCategoryIcon } from "./category-icon";

export function HistoryEventArticle({
event,
}: Readonly<{
event: CategorizedHistoryEvent;
}>) {
const categoryLabel = event.categoryLabel.toLocaleLowerCase("en-US");

return (
<article id={event.id}>
<header>
<p className="history-event-kicker">
<time dateTime={event.date}>{event.date}</time>
<Link
className="history-event-type"
data-analytics-event="history filter selected"
data-analytics-id={event.categoryId}
data-analytics-kind="history-category"
href={`${historyCategoryPath(event.categoryId)}#${event.id}`}
>
<HistoryCategoryIcon filterId={event.categoryId} />
<span>{categoryLabel}</span>
</Link>
{event.status === undefined ? null : (
<span className="history-event-status">{event.status}</span>
)}
{event.confidence === "confirmed" ? null : (
<span className="history-event-confidence">
{event.confidence}
</span>
)}
</p>
<h3>{event.title}</h3>
</header>
<p>{event.summary}</p>
{event.amount === undefined
&& event.metrics === undefined
&& event.details === undefined
? null
: (
<dl className="history-event-facts">
{event.amount === undefined ? null : (
<div>
<dt>amount</dt>
<dd>{event.amount.display}</dd>
</div>
)}
{event.metrics?.map((metric) => (
<div key={`${event.id}-${metric.label}`}>
<dt>{metric.label}</dt>
<dd>
{metric.value}
{metric.context === undefined ? null : ` · ${metric.context}`}
</dd>
</div>
))}
{event.details?.map((detail) => (
<div key={`${event.id}-${detail.label}`}>
<dt>{detail.label}</dt>
<dd>{detail.value}</dd>
</div>
))}
</dl>
)}
<p className="history-event-sources">
{event.sources.map((source, index) => (
<span key={source.url}>
{index === 0 ? null : <span aria-hidden="true"> · </span>}
<a
aria-label={`${source.publisher}: ${source.title}`}
data-analytics-event="source link opened"
data-analytics-id={event.id}
data-analytics-kind="history"
href={source.url}
>
{source.publisher}
</a>
</span>
))}
</p>
</article>
);
}
80 changes: 5 additions & 75 deletions app/history/history-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import type {
ValuationHeadlinePoint,
} from "@/lib/content";
import type { TimelineCategoryId } from "@/lib/history-schema";
import { historyCategoryPath } from "@/lib/history-urls";
import Link from "next/link";

import { HistoryEventArticle } from "./history-event-article";

import { HistoryCategoryIcon } from "./category-icon";
import {
historyFilterVisualStyle,
Expand Down Expand Up @@ -45,7 +48,7 @@ interface HistoryYear {
function categoryHref(
categoryId: TimelineCategoryId,
): `/history/${TimelineCategoryId}` {
return `/history/${categoryId}`;
return historyCategoryPath(categoryId);
}

function groupEventsByYear(
Expand Down Expand Up @@ -190,86 +193,13 @@ function HistoryMeasuresSidebar({
function HistoryEventItem({
event,
}: Readonly<{ event: CategorizedHistoryEvent }>) {
const categoryLabel = event.categoryLabel.toLocaleLowerCase("en-US");

return (
<li
className="history-event"
data-category={event.categoryId}
style={historyFilterVisualStyle(event.categoryId)}
>
<article id={event.id}>
<header>
<p className="history-event-kicker">
<time dateTime={event.date}>{event.date}</time>
<Link
className="history-event-type"
data-analytics-event="history filter selected"
data-analytics-id={event.categoryId}
data-analytics-kind="history-category"
href={`${categoryHref(event.categoryId)}#${event.id}`}
>
<HistoryCategoryIcon filterId={event.categoryId} />
<span>{categoryLabel}</span>
</Link>
{event.status === undefined ? null : (
<span className="history-event-status">{event.status}</span>
)}
{event.confidence === "confirmed" ? null : (
<span className="history-event-confidence">
{event.confidence}
</span>
)}
</p>
<h3>{event.title}</h3>
</header>
<p>{event.summary}</p>
{event.amount === undefined
&& event.metrics === undefined
&& event.details === undefined
? null
: (
<dl className="history-event-facts">
{event.amount === undefined ? null : (
<div>
<dt>amount</dt>
<dd>{event.amount.display}</dd>
</div>
)}
{event.metrics?.map((metric) => (
<div key={`${event.id}-${metric.label}`}>
<dt>{metric.label}</dt>
<dd>
{metric.value}
{metric.context === undefined ? null : ` · ${metric.context}`}
</dd>
</div>
))}
{event.details?.map((detail) => (
<div key={`${event.id}-${detail.label}`}>
<dt>{detail.label}</dt>
<dd>{detail.value}</dd>
</div>
))}
</dl>
)}
<p className="history-event-sources">
{event.sources.map((source, index) => (
<span key={source.url}>
{index === 0 ? null : <span aria-hidden="true"> · </span>}
<a
aria-label={`${source.publisher}: ${source.title}`}
data-analytics-event="source link opened"
data-analytics-id={event.id}
data-analytics-kind="history"
href={source.url}
>
{source.publisher}
</a>
</span>
))}
</p>
</article>
<HistoryEventArticle event={event} />
</li>
);
}
Expand Down
22 changes: 20 additions & 2 deletions app/history/valuation/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ describe("stripedex.com valuation history", () => {

expect(updatedSeo).toMatchObject({
description: expect.stringContaining("$200 billion 2027 company tender"),
lead: expect.stringMatching(
/\$200 billion in 2027.*not affiliated with, endorsed by, or operated by/su,
),
title: "Stripe Valuation History by Year, 2011–2027",
yearRange: "2011–2027",
});
Expand All @@ -66,11 +69,26 @@ describe("stripedex.com valuation history", () => {
});

test("renders every sourced observation with typed valuation context", async () => {
const seo = deriveValuationPageSeo(await loadHistory());
const history = await loadHistory();
const seo = deriveValuationPageSeo(history);
const latestHeadline = history.valuationHeadlines.at(-1);
if (latestHeadline === undefined) throw new Error("Missing latest valuation headline");
const html = renderToStaticMarkup(await ValuationPage());

expect(html).toContain(`<h1 class="stripedex-visually-hidden" id="valuation-page-heading">${seo.title}</h1>`);
expect(html).toContain(`<h1 class="history-page-title" id="valuation-page-heading">${seo.title}</h1>`);
expect(html).not.toContain('class="stripedex-visually-hidden"');
expect(html).toContain(seo.lead);
expect(html).toContain(seo.description);
expect(html).toContain("<table>");
expect(html).toContain('<th scope="col">year</th>');
expect(html).toContain('<th scope="col">valuation</th>');
expect(html).toContain('<th scope="col">basis</th>');
expect(html).toContain('<th scope="col">status</th>');
expect(html).toContain('<th scope="col">sources</th>');
expect(html).toContain(`<tr id="${latestHeadline.observationId}"`);
expect(html).toContain(`"numberOfItems":${history.valuationHeadlines.length}`);
expect(html).toContain(`https://stripedex.com/history/valuation#${latestHeadline.observationId}`);
expect(html).toContain(`${latestHeadline.calendarYear}: ${latestHeadline.display}`);
expect(html.match(/class="history-volume-chart-track"/gu)?.length).toBe(14);
expect(html.match(/class="history-valuation-basis-badge"/gu)?.length).toBe(25);
expect(html).toContain("company tender");
Expand Down
Loading