Skip to content

Commit d277715

Browse files
bloveclaude
andauthored
feat(website): first-person /about prose, sourced and guarded (#828)
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 <noreply@anthropic.com>
1 parent ac1d588 commit d277715

5 files changed

Lines changed: 93 additions & 13 deletions

File tree

apps/website/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./../../dist/apps/website/.next/types/routes.d.ts";
3+
import "./.next/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

apps/website/src/app/about/page.spec.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, it, vi } from 'vitest';
55
import { render, screen } from '@testing-library/react';
66
import AboutPage from './page';
77
import { getAuthor } from '../../lib/blog-authors';
8+
import { ABOUT_PARAGRAPHS } from '../../lib/about-content';
89

910
vi.mock('../../components/ui/Container', () => ({
1011
Container: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
@@ -24,17 +25,36 @@ const author = getAuthor('brian');
2425
* claims back to the author record rather than restating the layout.
2526
*/
2627
describe('AboutPage', () => {
27-
it('renders the bio verbatim from the author record', () => {
28+
it('renders each sourced paragraph verbatim', () => {
2829
render(<AboutPage />);
29-
// Not a substring match: any editorial embellishment around the sourced
30-
// sentence would still be a claim nothing in the repo supports.
31-
expect(screen.getByText(author.bio as string).textContent).toBe(author.bio);
30+
// Not substring matches: editorial embellishment around a sourced sentence
31+
// would still be a claim nothing citable supports.
32+
for (const paragraph of ABOUT_PARAGRAPHS) {
33+
expect(screen.getByText(paragraph).textContent).toBe(paragraph);
34+
}
3235
});
3336

34-
it('states the name and role the author record gives, and no other', () => {
37+
it('adds no biographical prose beyond the sourced paragraphs', () => {
38+
// The real guard. Every claim on this page has to trace to about-content.ts,
39+
// whose provenance comment names the source; a paragraph appearing here that
40+
// is not in that module is exactly the fabrication this page must not carry.
41+
const { container } = render(<AboutPage />);
42+
const sourced = new Set<string>(ABOUT_PARAGRAPHS);
43+
44+
const biography = container.querySelectorAll('section')[0];
45+
const paragraphs = Array.from(biography.querySelectorAll('p'))
46+
.map((node) => node.textContent ?? '')
47+
.filter((text) => text.split(' ').length > 12);
48+
49+
expect(paragraphs.length).toBe(ABOUT_PARAGRAPHS.length);
50+
for (const text of paragraphs) {
51+
expect(sourced.has(text), text).toBe(true);
52+
}
53+
});
54+
55+
it('names the author the record gives', () => {
3556
render(<AboutPage />);
36-
expect(screen.getByText(`Threadplane is written and maintained by ${author.name}, ${author.role}.`))
37-
.toBeTruthy();
57+
expect(screen.getByText(ABOUT_PARAGRAPHS[0]).textContent).toContain(author.name);
3858
});
3959

4060
it('links the GitHub profile the record names', () => {

apps/website/src/app/about/page.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import { aboutPageJsonLd, REPOSITORY_URL } from '../../lib/structured-data';
99
import { getAuthor } from '../../lib/blog-authors';
1010
import { createPageMetadata } from '../../lib/site-metadata';
1111
import { LONG_SUBHEAD } from '../../lib/positioning';
12+
import {
13+
ABOUT_HISTORY,
14+
ABOUT_HISTORY_HEADING,
15+
ABOUT_INTRO,
16+
ABOUT_PERSONAL,
17+
ABOUT_PRINCIPLES,
18+
} from '../../lib/about-content';
1219

1320
/**
1421
* The single author record the site already publishes (blog bylines read the
@@ -68,10 +75,18 @@ export default function AboutPage() {
6875
>
6976
Who writes Threadplane
7077
</h1>
71-
<p style={bodyStyle}>
72-
Threadplane is written and maintained by {author.name}, {author.role}.
73-
</p>
74-
<p style={bodyStyle}>{author.bio}</p>
78+
<p style={bodyStyle}>{ABOUT_INTRO}</p>
79+
80+
<h2 style={{ ...headingStyle, marginTop: 32 }}>{ABOUT_HISTORY_HEADING}</h2>
81+
{ABOUT_HISTORY.map((paragraph) => (
82+
<p key={paragraph} style={bodyStyle}>
83+
{paragraph}
84+
</p>
85+
))}
86+
87+
<p style={bodyStyle}>{ABOUT_PRINCIPLES}</p>
88+
<p style={bodyStyle}>{ABOUT_PERSONAL}</p>
89+
7590
<p style={{ ...bodyStyle, marginBottom: 0 }}>
7691
<a
7792
href={`https://github.com/${author.github}`}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
/**
3+
* The first-person prose rendered on /about.
4+
*
5+
* PROVENANCE: every biographical claim below is sourced from Brian Love's own
6+
* published writing at https://brianflove.com/about-me/ (retrieved 2026-08-24).
7+
* `PRINCIPLES` and `PERSONAL` are verbatim from that page; the rest is the same
8+
* material condensed, with no detail added.
9+
*
10+
* This page is what search engines read as an authorship and expertise signal,
11+
* which makes anything invented here both durable and machine-readable. Adding
12+
* a claim requires a citable source, not an inference — no years of experience,
13+
* employers, talks, awards, education, or clients that are not already public.
14+
*
15+
* The prose lives here rather than inline in the route so the spec can assert
16+
* that the page renders exactly these paragraphs and no others.
17+
*/
18+
19+
export const ABOUT_INTRO =
20+
"I'm Brian Love, an agentic software architect building developer tooling for fullstack AI-powered web applications. Threadplane is the framework I build and maintain for agent UI in Angular: durable threads, interrupts, subagents, planning, memory, and generative UI.";
21+
22+
export const ABOUT_HISTORY_HEADING = 'How I got here';
23+
24+
export const ABOUT_HISTORY: readonly string[] = [
25+
'I started as an intern and then a web developer at Hamilton College, and became CTO of Webucator, an enterprise training company. I have worked remotely since 2010. In 2019 I founded LiveLoveApp to build an engineering culture around excellence and health — high standards and a sustainable pace at the same time.',
26+
'In 2022 Mike Ryan joined LiveLoveApp and we built Polaris, an AI-powered site reliability platform that detected outages and incidents in web applications in real time on a 7KB SDK. Polaris won a $25,000 early-stage investment from Portland Seed Fund at the 2023 Bend Venture Conference.',
27+
'I started building on the ChatGPT APIs when GPT-3.5 launched. From there I focused on integrating AI into web applications for financial firms — surfacing model output in complex UIs, handling sensitive structured data, and augmenting decisions rather than replacing them. I built RAG pipelines on Azure AI Search, indexing enterprise content from SharePoint into vector stores. That work taught me how retrieval actually behaves in production: the chunking tradeoffs, the reranking strategies, and how to keep answers grounded when the stakes are high.',
28+
'In 2025 Mike and I co-created Hashbrown, a framework for building fullstack agentic web applications with LLMs.',
29+
] as const;
30+
31+
/** Verbatim from https://brianflove.com/about-me/. */
32+
export const ABOUT_PRINCIPLES =
33+
'Most of my work lives where product strategy, frontend architecture, developer experience, and AI interaction design overlap. I care about software that is useful in production, understandable to the team, and honest about the tradeoffs.';
34+
35+
/** Verbatim from https://brianflove.com/about-me/, less the sentence naming the city. */
36+
export const ABOUT_PERSONAL =
37+
'I live in Bend, Oregon with my wife Bonnie and our daughter Evelyn. I am a Christian, and that shapes how I think about ambition, responsibility, and the kind of work worth doing.';
38+
39+
/** Every paragraph the biography section renders, in order. */
40+
export const ABOUT_PARAGRAPHS: readonly string[] = [
41+
ABOUT_INTRO,
42+
...ABOUT_HISTORY,
43+
ABOUT_PRINCIPLES,
44+
ABOUT_PERSONAL,
45+
] as const;

apps/website/src/lib/blog-authors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const blogAuthors: Record<string, Author> = {
1818
brian: {
1919
name: 'Brian Love',
2020
role: 'Founder, Threadplane',
21-
bio: 'Angular consultant and open-source maintainer. Building agent UI for Angular teams.',
21+
bio: 'Agentic software architect building developer tooling for fullstack AI-powered web applications.',
2222
knowsAbout: ['Angular', 'TypeScript', 'LangGraph', 'AG-UI', 'Generative UI', 'Agent user interfaces'],
2323
github: 'blove',
2424
},

0 commit comments

Comments
 (0)