diff --git a/app/AGENTS.md b/app/AGENTS.md index ccc51b3..5494d6b 100644 --- a/app/AGENTS.md +++ b/app/AGENTS.md @@ -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. diff --git a/app/globals.css b/app/globals.css index 6c20405..e823954 100644 --- a/app/globals.css +++ b/app/globals.css @@ -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; diff --git a/app/history/[category]/page.test.tsx b/app/history/[category]/page.test.tsx index c46f304..4549dd7 100644 --- a/app/history/[category]/page.test.tsx +++ b/app/history/[category]/page.test.tsx @@ -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"'); diff --git a/app/history/history-event-article.tsx b/app/history/history-event-article.tsx new file mode 100644 index 0000000..2b47558 --- /dev/null +++ b/app/history/history-event-article.tsx @@ -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 ( +
+
+

+ + + + {categoryLabel} + + {event.status === undefined ? null : ( + {event.status} + )} + {event.confidence === "confirmed" ? null : ( + + {event.confidence} + + )} +

+

{event.title}

+
+

{event.summary}

+ {event.amount === undefined + && event.metrics === undefined + && event.details === undefined + ? null + : ( +
+ {event.amount === undefined ? null : ( +
+
amount
+
{event.amount.display}
+
+ )} + {event.metrics?.map((metric) => ( +
+
{metric.label}
+
+ {metric.value} + {metric.context === undefined ? null : ` · ${metric.context}`} +
+
+ ))} + {event.details?.map((detail) => ( +
+
{detail.label}
+
{detail.value}
+
+ ))} +
+ )} +

+ {event.sources.map((source, index) => ( + + {index === 0 ? null : } + + {source.publisher} + + + ))} +

+
+ ); +} diff --git a/app/history/history-view.tsx b/app/history/history-view.tsx index caa9f04..9d4b57f 100644 --- a/app/history/history-view.tsx +++ b/app/history/history-view.tsx @@ -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, @@ -45,7 +48,7 @@ interface HistoryYear { function categoryHref( categoryId: TimelineCategoryId, ): `/history/${TimelineCategoryId}` { - return `/history/${categoryId}`; + return historyCategoryPath(categoryId); } function groupEventsByYear( @@ -190,86 +193,13 @@ function HistoryMeasuresSidebar({ function HistoryEventItem({ event, }: Readonly<{ event: CategorizedHistoryEvent }>) { - const categoryLabel = event.categoryLabel.toLocaleLowerCase("en-US"); - return (
  • -
    -
    -

    - - - - {categoryLabel} - - {event.status === undefined ? null : ( - {event.status} - )} - {event.confidence === "confirmed" ? null : ( - - {event.confidence} - - )} -

    -

    {event.title}

    -
    -

    {event.summary}

    - {event.amount === undefined - && event.metrics === undefined - && event.details === undefined - ? null - : ( -
    - {event.amount === undefined ? null : ( -
    -
    amount
    -
    {event.amount.display}
    -
    - )} - {event.metrics?.map((metric) => ( -
    -
    {metric.label}
    -
    - {metric.value} - {metric.context === undefined ? null : ` · ${metric.context}`} -
    -
    - ))} - {event.details?.map((detail) => ( -
    -
    {detail.label}
    -
    {detail.value}
    -
    - ))} -
    - )} -

    - {event.sources.map((source, index) => ( - - {index === 0 ? null : } - - {source.publisher} - - - ))} -

    -
    +
  • ); } diff --git a/app/history/valuation/page.test.tsx b/app/history/valuation/page.test.tsx index 4ba8393..b7ddcb3 100644 --- a/app/history/valuation/page.test.tsx +++ b/app/history/valuation/page.test.tsx @@ -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", }); @@ -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(`

    ${seo.title}

    `); + expect(html).toContain(`

    ${seo.title}

    `); + expect(html).not.toContain('class="stripedex-visually-hidden"'); + expect(html).toContain(seo.lead); expect(html).toContain(seo.description); + expect(html).toContain(""); + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain(`> = { - "common-stock-409a": "common-stock 409A", - "market-indication": "market indication", - "post-money": "post-money", - "pre-money": "pre-money", - "transaction-implied": "transaction implied", - unspecified: "basis not specified", -}; - -const statusLabel: Readonly> = { - "agreements-signed": "agreements signed", - "company-confirmed": "company confirmed", - completed: "completed", - reported: "reported", - retrospective: "retrospective", -}; - const financingStageLabel: Readonly["stage"], string @@ -72,6 +58,8 @@ function partialDateLabel(date: string): string { export default async function ValuationPage() { const history = await loadHistory(); const seo = deriveValuationPageSeo(history); + const headlines = deriveValuationHeadlineRows(history); + const headlineIds = new Set(headlines.map((row) => row.observationId)); const maximumValue = Math.max( ...history.valuationHeadlines.map(({ valueUsd }) => valueUsd), ); @@ -81,9 +69,9 @@ export default async function ValuationPage() { ({ - id: observation.id, - title: `${observation.effective_date}: ${observation.title}`, + headlines.map((row) => ({ + id: row.observationId, + title: `${row.calendarYear}: ${row.display}`, })), { description: seo.description, @@ -107,21 +95,63 @@ export default async function ValuationPage() { aria-labelledby="valuation-page-heading" className="stripedex-section history-volume-page" > -

    +

    {seo.title}

    - Stripe’s private-company valuation has been reported through - financing and employee-tender terms, internal 409A marks, investor - secondaries, and secondary-market indications. These measurements - are not interchangeable, so the record preserves each one with its - date, basis, confidence, and sources. + {seo.lead}

    +
    +

    yearly headlines

    +
    +
    yearvaluationbasisstatussources
    + + + + + + + + + + + {headlines.map((row) => ( + + + + + + + + ))} + +
    yearvaluationbasisstatussources
    {row.calendarYear}{row.display}{row.basisLabel}{row.statusLabel} + {row.sources.map((source, index) => ( + + {index === 0 ? null : } + + {source.publisher} + + + ))} +
    + + +

    valuation by year

    @@ -147,7 +177,7 @@ export default async function ValuationPage() {