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 apps/website/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
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.
34 changes: 27 additions & 7 deletions apps/website/src/app/about/page.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => <div>{children}</div>,
Expand All @@ -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(<AboutPage />);
// 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(<AboutPage />);
const sourced = new Set<string>(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(<AboutPage />);
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', () => {
Expand Down
23 changes: 19 additions & 4 deletions apps/website/src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,10 +75,18 @@ export default function AboutPage() {
>
Who writes Threadplane
</h1>
<p style={bodyStyle}>
Threadplane is written and maintained by {author.name}, {author.role}.
</p>
<p style={bodyStyle}>{author.bio}</p>
<p style={bodyStyle}>{ABOUT_INTRO}</p>

<h2 style={{ ...headingStyle, marginTop: 32 }}>{ABOUT_HISTORY_HEADING}</h2>
{ABOUT_HISTORY.map((paragraph) => (
<p key={paragraph} style={bodyStyle}>
{paragraph}
</p>
))}

<p style={bodyStyle}>{ABOUT_PRINCIPLES}</p>
<p style={bodyStyle}>{ABOUT_PERSONAL}</p>

<p style={{ ...bodyStyle, marginBottom: 0 }}>
<a
href={`https://github.com/${author.github}`}
Expand Down
45 changes: 45 additions & 0 deletions apps/website/src/lib/about-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
/**
* The first-person prose rendered on /about.
*
* PROVENANCE: every biographical claim below is sourced from Brian Love's own
* published writing at https://brianflove.com/about-me/ (retrieved 2026-08-24).
* `PRINCIPLES` and `PERSONAL` are verbatim from that page; the rest is the same
* material condensed, with no detail added.
*
* This page is what search engines read as an authorship and expertise signal,
* which makes anything invented here both durable and machine-readable. Adding
* a claim requires a citable source, not an inference — no years of experience,
* employers, talks, awards, education, or clients that are not already public.
*
* The prose lives here rather than inline in the route so the spec can assert
* that the page renders exactly these paragraphs and no others.
*/

export const ABOUT_INTRO =
"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.";

export const ABOUT_HISTORY_HEADING = 'How I got here';

export const ABOUT_HISTORY: readonly string[] = [
'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.',
'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.',
'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.',
'In 2025 Mike and I co-created Hashbrown, a framework for building fullstack agentic web applications with LLMs.',
] as const;

/** Verbatim from https://brianflove.com/about-me/. */
export const ABOUT_PRINCIPLES =
'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.';

/** Verbatim from https://brianflove.com/about-me/, less the sentence naming the city. */
export const ABOUT_PERSONAL =
'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.';

/** Every paragraph the biography section renders, in order. */
export const ABOUT_PARAGRAPHS: readonly string[] = [
ABOUT_INTRO,
...ABOUT_HISTORY,
ABOUT_PRINCIPLES,
ABOUT_PERSONAL,
] as const;
2 changes: 1 addition & 1 deletion apps/website/src/lib/blog-authors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const blogAuthors: Record<string, Author> = {
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',
},
Expand Down
Loading