From b98ff63a2f870efcff9bf9143370e41a230d4f07 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 24 Aug 2026 10:10:58 -0700 Subject: [PATCH] feat(website): first-person /about prose, sourced and guarded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page shipped in #826 as a factual skeleton: a name, a role, and a one-line bio, with the connective sentences written by an agent and flagged for replacement. Replace them with Brian's own account, in first person. Every biographical claim traces to brianflove.com/about-me (retrieved 2026-08-24), and two paragraphs are verbatim from it. The prose lives in `about-content.ts` rather than inline in the route for one reason: the module carries a provenance comment naming the source, and the spec can assert the page renders exactly those paragraphs and no others. That preserves what the original spec was built to do — this page is an E-E-A-T signal, so it is precisely the surface a fabricated credential would appear on, and structured data makes such a claim durable and machine-readable. The guard is mutation-tested: injecting an invented "twenty years of experience and a Google Developer Expert award" paragraph fails the suite. `bio` moves from "Angular consultant and open-source maintainer" to Brian's current self-description. It feeds every blog byline and the /about meta description, so the site now states one role rather than two that disagree. `knowsAbout` is deliberately unchanged. It renders as a schema.org expertise claim and its comment scopes it to subjects with docs and code in this repository. Brian's RAG and Azure AI Search work is real but not evidenced here, and structured data is the wrong place to assert what the site cannot show. Deliberately absent: any statement of how Threadplane relates to Hashbrown. Hashbrown is credited to Brian and Mike Ryan; the relationship between the projects is Brian's to characterise, not something to infer. Co-Authored-By: Claude Opus 5 --- apps/website/next-env.d.ts | 2 +- apps/website/src/app/about/page.spec.tsx | 34 ++++++++++++++---- apps/website/src/app/about/page.tsx | 23 +++++++++--- apps/website/src/lib/about-content.ts | 45 ++++++++++++++++++++++++ apps/website/src/lib/blog-authors.ts | 2 +- 5 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 apps/website/src/lib/about-content.ts diff --git a/apps/website/next-env.d.ts b/apps/website/next-env.d.ts index fdbfe5258..c4b7818fb 100644 --- a/apps/website/next-env.d.ts +++ b/apps/website/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./../../dist/apps/website/.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/website/src/app/about/page.spec.tsx b/apps/website/src/app/about/page.spec.tsx index ffa3d47ed..6e4b41964 100644 --- a/apps/website/src/app/about/page.spec.tsx +++ b/apps/website/src/app/about/page.spec.tsx @@ -5,6 +5,7 @@ import { describe, expect, it, vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import AboutPage from './page'; import { getAuthor } from '../../lib/blog-authors'; +import { ABOUT_PARAGRAPHS } from '../../lib/about-content'; vi.mock('../../components/ui/Container', () => ({ Container: ({ children }: { children: React.ReactNode }) =>
{children}
, @@ -24,17 +25,36 @@ const author = getAuthor('brian'); * claims back to the author record rather than restating the layout. */ describe('AboutPage', () => { - it('renders the bio verbatim from the author record', () => { + it('renders each sourced paragraph verbatim', () => { render(); - // Not a substring match: any editorial embellishment around the sourced - // sentence would still be a claim nothing in the repo supports. - expect(screen.getByText(author.bio as string).textContent).toBe(author.bio); + // Not substring matches: editorial embellishment around a sourced sentence + // would still be a claim nothing citable supports. + for (const paragraph of ABOUT_PARAGRAPHS) { + expect(screen.getByText(paragraph).textContent).toBe(paragraph); + } }); - it('states the name and role the author record gives, and no other', () => { + it('adds no biographical prose beyond the sourced paragraphs', () => { + // The real guard. Every claim on this page has to trace to about-content.ts, + // whose provenance comment names the source; a paragraph appearing here that + // is not in that module is exactly the fabrication this page must not carry. + const { container } = render(); + const sourced = new Set(ABOUT_PARAGRAPHS); + + const biography = container.querySelectorAll('section')[0]; + const paragraphs = Array.from(biography.querySelectorAll('p')) + .map((node) => node.textContent ?? '') + .filter((text) => text.split(' ').length > 12); + + expect(paragraphs.length).toBe(ABOUT_PARAGRAPHS.length); + for (const text of paragraphs) { + expect(sourced.has(text), text).toBe(true); + } + }); + + it('names the author the record gives', () => { render(); - expect(screen.getByText(`Threadplane is written and maintained by ${author.name}, ${author.role}.`)) - .toBeTruthy(); + expect(screen.getByText(ABOUT_PARAGRAPHS[0]).textContent).toContain(author.name); }); it('links the GitHub profile the record names', () => { diff --git a/apps/website/src/app/about/page.tsx b/apps/website/src/app/about/page.tsx index 696662841..0f63937a9 100644 --- a/apps/website/src/app/about/page.tsx +++ b/apps/website/src/app/about/page.tsx @@ -9,6 +9,13 @@ import { aboutPageJsonLd, REPOSITORY_URL } from '../../lib/structured-data'; import { getAuthor } from '../../lib/blog-authors'; import { createPageMetadata } from '../../lib/site-metadata'; import { LONG_SUBHEAD } from '../../lib/positioning'; +import { + ABOUT_HISTORY, + ABOUT_HISTORY_HEADING, + ABOUT_INTRO, + ABOUT_PERSONAL, + ABOUT_PRINCIPLES, +} from '../../lib/about-content'; /** * The single author record the site already publishes (blog bylines read the @@ -68,10 +75,18 @@ export default function AboutPage() { > Who writes Threadplane -

- Threadplane is written and maintained by {author.name}, {author.role}. -

-

{author.bio}

+

{ABOUT_INTRO}

+ +

{ABOUT_HISTORY_HEADING}

+ {ABOUT_HISTORY.map((paragraph) => ( +

+ {paragraph} +

+ ))} + +

{ABOUT_PRINCIPLES}

+

{ABOUT_PERSONAL}

+

= { brian: { name: 'Brian Love', role: 'Founder, Threadplane', - bio: 'Angular consultant and open-source maintainer. Building agent UI for Angular teams.', + bio: 'Agentic software architect building developer tooling for fullstack AI-powered web applications.', knowsAbout: ['Angular', 'TypeScript', 'LangGraph', 'AG-UI', 'Generative UI', 'Agent user interfaces'], github: 'blove', },