From 66c67e517818ae5e50dcd6f992525a9a2ade8002 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Mon, 31 Aug 2026 18:04:48 -0700
Subject: [PATCH 1/7] docs(website): design spec for pricing page tightening
Co-Authored-By: Claude Fable 5
---
.../2026-08-31-pricing-tighten-design.md | 72 +++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100644 docs/superpowers/specs/2026-08-31-pricing-tighten-design.md
diff --git a/docs/superpowers/specs/2026-08-31-pricing-tighten-design.md b/docs/superpowers/specs/2026-08-31-pricing-tighten-design.md
new file mode 100644
index 000000000..0786b227e
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-31-pricing-tighten-design.md
@@ -0,0 +1,72 @@
+# Pricing page tightening
+
+**Date:** 2026-08-31
+**Status:** Approved (both cuts confirmed; LeadForm FORM explicitly retained).
+**Branch:** `blove/pricing-tighten`
+
+## Problem
+
+The pricing page is ~7,050px and says "everything is MIT" six times: hero
+license line, Community card, boundary section, the comparison table's
+six-row all-"Included" SOFTWARE block, four of ten FAQ items, and the CTA
+caption. The compatibility section spends ~700px on one sentence (two of its
+four rows are "—"). The LeadForm section restates the tiers a third time in
+four checkmark cards. Headers are centered while the site now speaks rails.
+The three tier cards cluster left with dead space right (grid not centered).
+
+## Changes
+
+1. **Comparison table** (`PricingDetails.tsx` / `PricingComparison`): delete
+ the SOFTWARE section (all six rows + its group header). Above the table,
+ one line replaces it: "The software is identical on every path — MIT, all
+ of it. What changes is support." (rendered as the section's lede,
+ replacing "The software stays open. The level of support and delivery
+ changes."). The SUPPORT AND DELIVERY group header becomes unnecessary
+ with one group — remove it; the seven remaining rows stand alone.
+2. **Compatibility section**: the `` in `pricing/page.tsx` and
+ `CompatibilityMatrix` usage are deleted. One compatibility line joins the
+ hero license strip: "Every package is MIT · Commercial use without
+ registration or runtime checks · Angular 20–22, CI-tested · No
+ Threadplane cloud". `CompatibilityMatrix.tsx` + its spec are deleted IF
+ the homepage/docs don't consume it (grep first; if consumed elsewhere,
+ only the pricing usage is removed).
+ Note: `angular-support.mjs` in the pricing dir may generate the versions —
+ if so, the hero line should consume the same source (single source of
+ truth for "20–22"); investigate at plan time.
+3. **FAQ 10 → 6** (`PricingFAQ.tsx`): delete "Is Threadplane free?",
+ "Can I use every package commercially?", "Does a paid plan unlock
+ different software?", "Can I modify or redistribute the source?". Keep:
+ cloud service, conversations/data storage, model/hosting costs, what am I
+ paying for, Production Assurance, Pilot-to-Prod. Update `PricingFAQ.spec`
+ accordingly (it exists).
+4. **LeadForm** (`LeadForm.tsx`): the FORM and all its fields, states, and
+ analytics are UNTOUCHED. Delete the four checkmark cards; the section
+ becomes rail header + the framing sentence + the form, with the form
+ allowed the visual center (single-column layout, form max-width ~640px).
+ The "See how Pilot-to-Prod works →" link from the deleted cards moves
+ under the framing sentence (do not lose the link).
+5. **Rail headers** on: boundary section ("What you are buying"), comparison
+ ("Full comparison"), FAQ ("Questions"), LeadForm ("Enterprise") — the
+ local railkick pattern (kicker + hairline), left-aligned. Hero stays
+ centered (it is the page's one centered moment, like the homepage hero).
+6. **Tier card grid centered** (`CompareTable`): the three cards center in
+ the container (fix the left-cluster + dead right column).
+
+## Testing
+
+- `pricing/page.spec.tsx` exists — update assertions minimally where they
+ reference removed content (grep for compat/FAQ strings first).
+- `PricingFAQ.spec` updated to the six survivors.
+- `CompareTable.spec` untouched unless the centering fix moves DOM.
+- New `LeadForm.spec` pinning: submit fires its analytics events and
+ renders its success state (mock fetch; same pattern as
+ WhitePaperBlock.spec) — written BEFORE the layout change, passing before
+ and after.
+- Full suite green (48+ files, all green baseline); prod build; Chrome MCP
+ visual pass at 1440px + 375px (the pane's hidden-viewport artifacts make
+ Chrome the verification surface for this arc).
+
+## Out of scope
+
+Tier copy/structure inside the cards; the boundary diagram's three bands;
+FinalCTA (default variant is correct here); the /contact page.
From 0f3198c7755062eb3027832c9b2c1838677f774f Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Mon, 31 Aug 2026 18:07:46 -0700
Subject: [PATCH 2/7] docs(website): implementation plan for pricing tightening
Co-Authored-By: Claude Fable 5
---
.../plans/2026-08-31-pricing-tighten.md | 344 ++++++++++++++++++
1 file changed, 344 insertions(+)
create mode 100644 docs/superpowers/plans/2026-08-31-pricing-tighten.md
diff --git a/docs/superpowers/plans/2026-08-31-pricing-tighten.md b/docs/superpowers/plans/2026-08-31-pricing-tighten.md
new file mode 100644
index 000000000..762e8a5e6
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-31-pricing-tighten.md
@@ -0,0 +1,344 @@
+# Pricing Page Tightening Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Cut the pricing page's repetition per `docs/superpowers/specs/2026-08-31-pricing-tighten-design.md` — one MIT statement, no all-"Included" table block, no 700px compat table, no third tier restatement beside the lead form — while pinning the LeadForm's behavior untouched.
+
+**Architecture:** Next.js 16 / React 19. Pricing components in `apps/website/src/components/pricing/`; pricing CSS in `apps/website/src/styles/marketing.css` (pricing-*, lead-form-* classes). `angular-support.mjs` is the single source of truth for supported majors — the hero's new compat clause must consume it, not hardcode. UNLAYERED CSS, no inline styles.
+
+**Test command:** `cd apps/website && npx vitest run --config vite.config.mts` — baseline is ALL GREEN (48 files / 389+ tests). Any failure is yours.
+**Branch:** `blove/pricing-tighten`, expected starting HEAD `66c67e51` or descendant.
+**Visual verification surface:** Chrome MCP (the in-app pane returns blank frames after scrolls) — but subagents don't drive Chrome; the controller runs the visual gate. Subagents verify via tests + DOM-free reasoning.
+
+---
+
+### Task 1: Pin the LeadForm, then strip it to header + form
+
+**Files:**
+- Create: `apps/website/src/components/pricing/LeadForm.spec.tsx`
+- Modify: `apps/website/src/components/pricing/LeadForm.tsx`
+- Modify: `apps/website/src/styles/marketing.css`
+
+- [ ] **Step 1: Write the pinning spec against the UNMODIFIED component**
+
+Read `LeadForm.tsx` first: its submit handler's analytics events (grep `track(` — events around lines 43–68 use `source_section: 'lead-form'`), the field labels/placeholders, the success-state text. Write a spec in the WhitePaperBlock.spec pattern:
+
+```tsx
+// apps/website/src/components/pricing/LeadForm.spec.tsx
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { LeadForm } from './LeadForm';
+
+const trackMock = vi.fn();
+vi.mock('../../lib/analytics/client', async (importOriginal) => {
+ const mod = await importOriginal>();
+ return { ...mod, track: (...args: unknown[]) => trackMock(...args) };
+});
+
+describe('LeadForm', () => {
+ beforeEach(() => {
+ trackMock.mockClear();
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true }));
+ });
+
+ it('submits and fires the lead analytics with the form payload', async () => {
+ render();
+ // Fill required fields — use the REAL labels from the component
+ // (Name / Work email / Company, the team-size + timeline selects,
+ // the Pilot-to-Prod radio). Adapt these queries to what exists:
+ fireEvent.change(screen.getByLabelText(/name/i), { target: { value: 'Dev' } });
+ fireEvent.change(screen.getByLabelText(/work email/i), { target: { value: 'dev@example.com' } });
+ fireEvent.change(screen.getByLabelText(/company/i), { target: { value: 'Acme' } });
+ fireEvent.submit(screen.getByRole('button', { name: /request enterprise quote/i }).closest('form')!);
+ await waitFor(() => {
+ const events = trackMock.mock.calls.map((c) => c[0]);
+ expect(events.some((e) => String(e).includes('lead'))).toBe(true);
+ });
+ });
+});
+```
+
+Adapt every literal to the real component (mocked module path, event names — use the EXACT event constants you find, not the `.includes('lead')` sketch; assert the success render if one exists). The spec must PASS unmodified. If required-field validation blocks a jsdom submit in a way that needs component changes, STOP (NEEDS_CONTEXT). Paste the passing run line.
+
+- [ ] **Step 2: Strip the section chrome**
+
+In `LeadForm.tsx`:
+- The header becomes the rail pattern:
+```tsx
+
+ Enterprise
+
+
+```
+ (heading + subhead keep their elements/ids below it, left-aligned).
+- DELETE the `
@@ -57,19 +59,6 @@ export default function PricingPage() {
-
-
- Compatibility
-
- Angular version support
-
-
- We ship against the versions our CI tests. Other versions may work but aren't guaranteed.
-
-
-
-
-
diff --git a/apps/website/src/components/pricing/CompatibilityMatrix.spec.tsx b/apps/website/src/components/pricing/CompatibilityMatrix.spec.tsx
deleted file mode 100644
index b958696a3..000000000
--- a/apps/website/src/components/pricing/CompatibilityMatrix.spec.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-// SPDX-License-Identifier: MIT
-// @vitest-environment jsdom
-import React from 'react';
-import { describe, expect, it } from 'vitest';
-import { render, screen } from '@testing-library/react';
-import {
- WEBSITE_ANGULAR_SUPPORT_ROWS,
- WEBSITE_SUPPORTED_ANGULAR_MAJORS,
-} from './angular-support.mjs';
-import { CompatibilityMatrix } from './CompatibilityMatrix';
-
-describe('CompatibilityMatrix', () => {
- it('renders the structured Angular support buckets', () => {
- render();
- expect(WEBSITE_SUPPORTED_ANGULAR_MAJORS).toEqual([20, 21, 22]);
-
- expect(screen.getByRole('rowheader', { name: 'Supported' })).toBeTruthy();
- expect(screen.getByText('Angular 20, 21, 22')).toBeTruthy();
- expect(screen.getByText(/Experimental/)).toBeTruthy();
- expect(screen.getByRole('rowheader', { name: 'Planned' })).toBeTruthy();
- expect(screen.getAllByText('—')).toHaveLength(2);
- expect(
- WEBSITE_ANGULAR_SUPPORT_ROWS.some(
- (row) => row.label === 'Planned' && /Angular 22/.test(row.versions)
- )
- ).toBe(false);
- expect(screen.getByText(/Unsupported/)).toBeTruthy();
- expect(screen.getByText(/≤19/)).toBeTruthy();
- });
-
- it('uses semantic column and row headers', () => {
- render();
- expect(
- screen.getByRole('columnheader', { name: 'Status' }).getAttribute('scope')
- ).toBe('col');
- expect(
- screen
- .getByRole('columnheader', { name: 'Angular versions' })
- .getAttribute('scope')
- ).toBe('col');
- expect(
- screen.getByRole('rowheader', { name: 'Supported' }).getAttribute('scope')
- ).toBe('row');
- });
-});
diff --git a/apps/website/src/components/pricing/CompatibilityMatrix.tsx b/apps/website/src/components/pricing/CompatibilityMatrix.tsx
deleted file mode 100644
index 6181487ae..000000000
--- a/apps/website/src/components/pricing/CompatibilityMatrix.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-// SPDX-License-Identifier: MIT
-import React from 'react';
-import { WEBSITE_ANGULAR_SUPPORT_ROWS } from './angular-support.mjs';
-
-interface Row {
- readonly label: string;
- readonly versions: string;
- readonly tone: 'success' | 'warn' | 'info' | 'muted';
-}
-
-const ROWS: readonly Row[] = WEBSITE_ANGULAR_SUPPORT_ROWS;
-
-export function CompatibilityMatrix() {
- return (
-
-
-
-
- |
- Status
- |
-
- Angular versions
- |
-
-
-
- {ROWS.map((row) => (
-
- |
- {row.label}
- |
- {row.versions} |
-
- ))}
-
-
-
- );
-}
diff --git a/apps/website/src/components/pricing/angular-support.mjs b/apps/website/src/components/pricing/angular-support.mjs
index df57a83a3..f13cb3ff2 100644
--- a/apps/website/src/components/pricing/angular-support.mjs
+++ b/apps/website/src/components/pricing/angular-support.mjs
@@ -4,25 +4,3 @@ export const WEBSITE_SUPPORTED_ANGULAR_MAJORS = Object.freeze([20, 21, 22]);
export const WEBSITE_SUPPORTED_ANGULAR_VERSIONS = `Angular ${WEBSITE_SUPPORTED_ANGULAR_MAJORS.join(
', '
)}`;
-
-const WEBSITE_SUPPORTED_ANGULAR_SUMMARY = `Angular ${WEBSITE_SUPPORTED_ANGULAR_MAJORS.slice(
- 0,
- -1
-).join(', ')}, and ${WEBSITE_SUPPORTED_ANGULAR_MAJORS.at(-1)}`;
-
-export const WEBSITE_PRICING_SUPPORT_SUMMARY = `${WEBSITE_SUPPORTED_ANGULAR_SUMMARY} support`;
-
-export const WEBSITE_ANGULAR_SUPPORT_ROWS = Object.freeze([
- Object.freeze({
- label: 'Supported',
- versions: WEBSITE_SUPPORTED_ANGULAR_VERSIONS,
- tone: 'success',
- }),
- Object.freeze({ label: 'Experimental', versions: '—', tone: 'warn' }),
- Object.freeze({ label: 'Planned', versions: '—', tone: 'info' }),
- Object.freeze({
- label: 'Unsupported',
- versions: 'Angular ≤19',
- tone: 'muted',
- }),
-]);
diff --git a/apps/website/src/styles/marketing.css b/apps/website/src/styles/marketing.css
index d3c12b0ec..b7451d263 100644
--- a/apps/website/src/styles/marketing.css
+++ b/apps/website/src/styles/marketing.css
@@ -524,46 +524,6 @@
margin: 0;
}
-/* CompatibilityMatrix — components/pricing/CompatibilityMatrix.tsx */
-.compat-matrix {
- border: 1px solid var(--color-border);
- border-radius: 8px;
- overflow: hidden;
-}
-.compat-matrix-table {
- width: 100%;
- border-collapse: collapse;
-}
-.compat-matrix-head-row {
- background: var(--color-surface);
-}
-.compat-matrix-th {
- text-align: left;
- padding: 12px 16px;
- font-weight: 600;
- color: var(--color-text-primary);
- font-size: var(--text-body);
- border-bottom: 1px solid var(--color-border);
-}
-.compat-matrix-td-label {
- text-align: left;
- padding: 12px 16px;
- font-size: var(--text-body);
- font-weight: 500;
- border-bottom: 1px solid var(--color-border);
-}
-.compat-matrix-td-label[data-tone="success"] { color: #16a34a; }
-.compat-matrix-td-label[data-tone="warn"] { color: #d97706; }
-.compat-matrix-td-label[data-tone="info"] { color: var(--color-accent); }
-.compat-matrix-td-label[data-tone="muted"] { color: var(--color-text-muted); }
-.compat-matrix-td-versions {
- padding: 12px 16px;
- font-size: var(--text-body);
- color: var(--color-text-secondary);
- font-family: var(--font-mono);
- border-bottom: 1px solid var(--color-border);
-}
-
/* PricingFAQ — components/pricing/PricingFAQ.tsx */
.pricing-faq-header {
text-align: center;
diff --git a/apps/website/src/styles/pages.css b/apps/website/src/styles/pages.css
index 77e7fc8ec..87b2781c3 100644
--- a/apps/website/src/styles/pages.css
+++ b/apps/website/src/styles/pages.css
@@ -1163,23 +1163,6 @@
.pricing-page-license-line strong {
color: var(--color-accent);
}
-.pricing-page-eyebrow-tight {
- margin-bottom: 12px;
-}
-.pricing-page-h2 {
- font-family: var(--font-garamond);
- font-size: var(--text-h2);
- margin: 0;
- margin-bottom: 16px;
- color: var(--color-text-primary);
-}
-.pricing-page-compat-body {
- margin: 0;
- margin-bottom: 24px;
- color: var(--color-text-secondary);
- max-width: 60ch;
-}
-
@media (max-width: 640px) {
.pricing-page-license-line {
align-items: flex-start;
From 9e16f9e8d70627dc0d14df28c5f8e26f52d183c1 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Mon, 31 Aug 2026 18:42:27 -0700
Subject: [PATCH 6/7] copy(website): pricing FAQ keeps only genuinely open
questions
---
.../src/components/pricing/PricingFAQ.spec.tsx | 8 ++------
.../src/components/pricing/PricingFAQ.tsx | 16 ----------------
2 files changed, 2 insertions(+), 22 deletions(-)
diff --git a/apps/website/src/components/pricing/PricingFAQ.spec.tsx b/apps/website/src/components/pricing/PricingFAQ.spec.tsx
index f48bc2e39..f59f0ccae 100644
--- a/apps/website/src/components/pricing/PricingFAQ.spec.tsx
+++ b/apps/website/src/components/pricing/PricingFAQ.spec.tsx
@@ -15,14 +15,10 @@ vi.mock('../ui/Eyebrow', () => ({
}));
const EXPECTED_QUESTIONS = [
- 'Is Threadplane free?',
- 'Can I use every package commercially?',
'Does Threadplane have a cloud service?',
'Does Threadplane store my conversations or agent data?',
'Are model or hosting costs included?',
'What am I paying for?',
- 'Does a paid plan unlock different software?',
- 'Can I modify or redistribute the source?',
'What is Production Assurance?',
'What is Pilot-to-Prod?',
];
@@ -48,8 +44,8 @@ describe('PricingFAQ', () => {
expect(container.querySelector('#faq')).toBeTruthy();
});
- it('states that all packages use MIT', () => {
+ it('describes Production Assurance as a scoped support relationship', () => {
render();
- expect(screen.getByText(/every published Threadplane package is MIT-licensed/i)).toBeTruthy();
+ expect(screen.getByText(/Production Assurance is a scoped support relationship/i)).toBeTruthy();
});
});
diff --git a/apps/website/src/components/pricing/PricingFAQ.tsx b/apps/website/src/components/pricing/PricingFAQ.tsx
index 9d7c4d9e8..dcb7b84fc 100644
--- a/apps/website/src/components/pricing/PricingFAQ.tsx
+++ b/apps/website/src/components/pricing/PricingFAQ.tsx
@@ -4,14 +4,6 @@ import { Eyebrow } from '../ui/Eyebrow';
import { FAQ, type FAQItem } from '../ui/FAQ';
const ITEMS: FAQItem[] = [
- {
- q: 'Is Threadplane free?',
- a: 'Yes. Every published Threadplane package is MIT-licensed and free to use, modify, and redistribute under the MIT terms.',
- },
- {
- q: 'Can I use every package commercially?',
- a: 'Yes. The MIT license permits commercial products, internal tools, client work, and hosted services. There are no paid-use activation steps or runtime checks.',
- },
{
q: 'Does Threadplane have a cloud service?',
a: 'No. Threadplane runs inside your Angular application. You operate your application, agent runtime, data stores, hosting, and model-provider accounts.',
@@ -28,14 +20,6 @@ const ITEMS: FAQItem[] = [
q: 'What am I paying for?',
a: 'Paid engagements provide expert support, architecture guidance, response commitments, security and procurement assistance, or hands-on delivery. The software remains MIT-licensed.',
},
- {
- q: 'Does a paid plan unlock different software?',
- a: 'No. Community users and paid customers run the same packages with the same core capabilities. Paid relationships add services and accountability.',
- },
- {
- q: 'Can I modify or redistribute the source?',
- a: 'Yes. You may modify and redistribute the packages under the MIT license, including as part of commercial software. Preserve the copyright and permission notice as required by MIT.',
- },
{
q: 'What is Production Assurance?',
a: 'Production Assurance is a scoped support relationship for teams running Threadplane in production. It can include private support, response commitments, architecture reviews, migration guidance, and security assistance.',
From 33be325084dfeadfdf1747c2934028d7ce4f2dcb Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Mon, 31 Aug 2026 18:46:38 -0700
Subject: [PATCH 7/7] refactor(website): pricing rail headers and centered tier
grid
---
.../src/components/pricing/PricingDetails.tsx | 10 ++++++++--
.../src/components/pricing/PricingFAQ.tsx | 5 ++++-
apps/website/src/styles/marketing.css | 18 +++++++++++++++---
3 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/apps/website/src/components/pricing/PricingDetails.tsx b/apps/website/src/components/pricing/PricingDetails.tsx
index dc62662c7..ff9d3f6c0 100644
--- a/apps/website/src/components/pricing/PricingDetails.tsx
+++ b/apps/website/src/components/pricing/PricingDetails.tsx
@@ -69,7 +69,10 @@ export function ArchitectureBoundary() {
return (
-
What you are buying
+
+
What you are buying
+
+
Open software. Commercial support when you want it.
@@ -117,7 +120,10 @@ export function PricingComparison() {
return (
-
Full comparison
+
What changes between paths.
diff --git a/apps/website/src/components/pricing/PricingFAQ.tsx b/apps/website/src/components/pricing/PricingFAQ.tsx
index dcb7b84fc..a50951449 100644
--- a/apps/website/src/components/pricing/PricingFAQ.tsx
+++ b/apps/website/src/components/pricing/PricingFAQ.tsx
@@ -35,7 +35,10 @@ export function PricingFAQ() {
-
Questions
+
+ Questions
+
+
Pricing FAQ.
diff --git a/apps/website/src/styles/marketing.css b/apps/website/src/styles/marketing.css
index b7451d263..34fc92feb 100644
--- a/apps/website/src/styles/marketing.css
+++ b/apps/website/src/styles/marketing.css
@@ -52,9 +52,11 @@
}
.pricing-plan-grid {
display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
align-items: stretch;
+ max-width: 1040px;
+ margin-inline: auto;
}
.pricing-plan-card {
position: relative;
@@ -203,6 +205,16 @@
max-width: 760px;
margin-bottom: 36px;
}
+.pricing-section-rail {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+}
+.pricing-section-rail-line {
+ flex: 1;
+ height: 1px;
+ background: var(--color-border);
+}
.pricing-section-kicker {
margin: 0 0 10px;
color: var(--color-accent);
@@ -230,7 +242,6 @@
.pricing-boundary,
.pricing-comparison {
max-width: 1200px;
- margin: 0 auto;
}
.pricing-boundary-flow {
display: grid;
@@ -354,6 +365,8 @@
.pricing-plan-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px 16px;
+ max-width: none;
+ margin-inline: 0;
}
.pricing-plan-description {
min-height: 0;
@@ -526,7 +539,6 @@
/* PricingFAQ — components/pricing/PricingFAQ.tsx */
.pricing-faq-header {
- text-align: center;
margin-bottom: 48px;
}
.pricing-faq-eyebrow {