From ef819474467356bd0d5f76c356a8a169edb823f0 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 22 May 2026 10:15:06 -0700 Subject: [PATCH 01/15] docs(licensing): align ThreadPlane branding, add /docs/licensing landing page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Editorial cleanup across all licensing-touching surfaces: - New /docs/licensing landing page (linked from email + footer + /thanks) - Email copy: "Thanks for subscribing" → "Thanks for your ThreadPlane license purchase"; CACHEPLANE_LICENSE → THREADPLANE_LICENSE in installation snippet; install snippet now shows provideChat() pattern (the actual API), not process.env-only - Brand: "Threadplane" → "ThreadPlane" in customer-visible copy and library NOTICE/README files - License name: always "ThreadPlane Commercial license" (capitalized) - Tense: drop "Starting with the next published version" from COMMERCIAL.md and root README — the license is live - Eval policy: precise "30 calendar days from first commercial use, good-faith, no telemetry" wording in libs/chat/COMMERCIAL-USE.md and PricingFAQ - Tier scoping: standardize "Indie" name, prefix paid features with "ThreadPlane Commercial license", drop Enterprise "starting at $10k/year" display period (was inconsistent with "Custom") - Footer "Licensing" link → /docs/licensing (was /pricing#faq) - /thanks page button → /docs/licensing (was /docs/chat/...) Co-Authored-By: Claude Opus 4.7 (1M context) --- COMMERCIAL.md | 8 +- README.md | 2 +- apps/minting-service/src/lib/email.spec.ts | 6 +- apps/minting-service/src/lib/email.ts | 34 +- apps/website/src/app/docs/licensing/page.tsx | 302 ++++++++++++++++++ apps/website/src/app/pricing/page.tsx | 6 +- apps/website/src/app/thanks/page.tsx | 6 +- .../src/components/pricing/PricingFAQ.tsx | 4 +- apps/website/src/components/shared/Footer.tsx | 8 +- libs/chat/COMMERCIAL-USE.md | 2 +- libs/chat/NOTICE.md | 4 +- libs/chat/README.md | 18 +- pricing/tiers.config.ts | 12 +- 13 files changed, 360 insertions(+), 52 deletions(-) create mode 100644 apps/website/src/app/docs/licensing/page.tsx diff --git a/COMMERCIAL.md b/COMMERCIAL.md index 963690f8..421a62ff 100644 --- a/COMMERCIAL.md +++ b/COMMERCIAL.md @@ -4,12 +4,10 @@ Most libraries in this repository — `@ngaf/render`, `@ngaf/agent`, `@ngaf/lang ## `@ngaf/chat` -Starting with the next published version, `@ngaf/chat` is dual-licensed: +`@ngaf/chat` is dual-licensed: -- **PolyForm Noncommercial 1.0.0** for free noncommercial use (personal, hobby, student, academic, nonprofit, public demos, OSI-licensed open source, 30-day commercial evaluation). -- **Threadplane commercial license** for commercial production use. - -Historical MIT releases of `@ngaf/chat` remain under their original terms. +- **PolyForm Noncommercial 1.0.0** for free noncommercial use (personal, hobby, student, academic, nonprofit, public demos, OSI-licensed open source, 30 calendar days of commercial evaluation from first commercial use). +- **ThreadPlane Commercial license** for commercial production use. Sold via [threadplane.ai/pricing](https://threadplane.ai/pricing); see [/docs/licensing](https://threadplane.ai/docs/licensing) for installation. See [`libs/chat/LICENSE.md`](./libs/chat/LICENSE.md), [`libs/chat/LICENSE-COMMERCIAL.md`](./libs/chat/LICENSE-COMMERCIAL.md), and [`libs/chat/COMMERCIAL-USE.md`](./libs/chat/COMMERCIAL-USE.md) for the full terms. diff --git a/README.md b/README.md index d818dc90..0e4c078c 100644 --- a/README.md +++ b/README.md @@ -131,4 +131,4 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them Most libraries in this repository (`@ngaf/render`, `@ngaf/agent`, `@ngaf/langgraph`, `@ngaf/ag-ui`, `@ngaf/a2ui`, `@ngaf/licensing`, `@ngaf/telemetry`, `@ngaf/design-tokens`) are released under the **MIT License** — free for any use, including commercial, with attribution. -**`@ngaf/chat`** is the exception. Future versions are licensed under **PolyForm Noncommercial 1.0.0 OR a Threadplane commercial license**. Historical npm releases remain MIT. See [`libs/chat/LICENSE.md`](./libs/chat/LICENSE.md), [`libs/chat/COMMERCIAL-USE.md`](./libs/chat/COMMERCIAL-USE.md), and [`COMMERCIAL.md`](./COMMERCIAL.md) for details. +**`@ngaf/chat`** is the exception. It is dual-licensed under **PolyForm Noncommercial 1.0.0** for free noncommercial use, or a **ThreadPlane Commercial license** for production use inside a for-profit context. See [`libs/chat/LICENSE.md`](./libs/chat/LICENSE.md), [`libs/chat/COMMERCIAL-USE.md`](./libs/chat/COMMERCIAL-USE.md), [`COMMERCIAL.md`](./COMMERCIAL.md), and [threadplane.ai/docs/licensing](https://threadplane.ai/docs/licensing) for details. diff --git a/apps/minting-service/src/lib/email.spec.ts b/apps/minting-service/src/lib/email.spec.ts index 19e575ee..43a81d24 100644 --- a/apps/minting-service/src/lib/email.spec.ts +++ b/apps/minting-service/src/lib/email.spec.ts @@ -10,9 +10,9 @@ describe('renderLicenseEmail', () => { expiresAt: new Date('2027-04-20T00:00:00Z'), }); - expect(out.text).toContain('-----BEGIN CACHEPLANE LICENSE-----'); + expect(out.text).toContain('-----BEGIN THREADPLANE LICENSE-----'); expect(out.text).toContain('PAYLOAD.SIG'); - expect(out.text).toContain('-----END CACHEPLANE LICENSE-----'); + expect(out.text).toContain('-----END THREADPLANE LICENSE-----'); }); it('subject includes tier and seat count with plural s for seats > 1', () => { @@ -54,6 +54,6 @@ describe('renderLicenseEmail', () => { }); expect(out.html).toContain(' + // .env + THREADPLANE_LICENSE= Docs: https://threadplane.ai/docs/licensing Questions: reply to this email. @@ -48,16 +51,21 @@ Questions: reply to this email. -- The ThreadPlane team `; - const html = `

Thanks for subscribing to ThreadPlane.

-

Your license token is below. Set it as the CACHEPLANE_LICENSE environment variable in your application:

-
-----BEGIN CACHEPLANE LICENSE-----
+  const html = `

Thanks for your ThreadPlane license purchase.

+

Your license is valid for 12 months from today. Paste the token below into your @ngaf/chat configuration:

+
-----BEGIN THREADPLANE LICENSE-----
 ${escapeHtml(vars.token)}
------END CACHEPLANE LICENSE-----
+-----END THREADPLANE LICENSE-----

Tier: ${escapeHtml(vars.tier)}
Seats: ${vars.seats}
Expires: ${escapeHtml(expiresIso)}

Installation:

-
export CACHEPLANE_LICENSE="<paste token above>"
+
provideChat({
+  license: process.env['THREADPLANE_LICENSE'],
+});
+
+// .env
+THREADPLANE_LICENSE=<paste token above>

Docs: threadplane.ai/docs/licensing
Questions: reply to this email.

-- The ThreadPlane team

diff --git a/apps/website/src/app/docs/licensing/page.tsx b/apps/website/src/app/docs/licensing/page.tsx new file mode 100644 index 00000000..21430f4d --- /dev/null +++ b/apps/website/src/app/docs/licensing/page.tsx @@ -0,0 +1,302 @@ +import Link from 'next/link'; +import { tokens } from '@ngaf/design-tokens'; +import { Container } from '../../../components/ui/Container'; +import { Section } from '../../../components/ui/Section'; +import { Eyebrow } from '../../../components/ui/Eyebrow'; +import { Button } from '../../../components/ui/Button'; +import { createPageMetadata } from '../../../lib/site-metadata'; + +export const metadata = createPageMetadata({ + title: 'Licensing — ThreadPlane', + description: + 'How the ThreadPlane Commercial license works, who needs one, and how to install your license token in @ngaf/chat.', + pathname: '/docs/licensing', + type: 'website', +}); + +const headingStyle = { + fontFamily: tokens.typography.h2.family, + fontSize: tokens.typography.h2.size, + lineHeight: tokens.typography.h2.line, + fontWeight: 700, + color: tokens.colors.textPrimary, + margin: 0, + marginBottom: 16, + letterSpacing: '-0.01em', +} as const; + +const h3Style = { + fontFamily: tokens.typography.h3.family, + fontSize: tokens.typography.h3.size, + lineHeight: tokens.typography.h3.line, + fontWeight: 600, + color: tokens.colors.textPrimary, + margin: 0, + marginTop: 24, + marginBottom: 8, +} as const; + +const bodyStyle = { + fontFamily: tokens.typography.body.family, + fontSize: tokens.typography.body.size, + lineHeight: tokens.typography.body.line, + color: tokens.colors.textSecondary, + margin: '0 0 16px', + maxWidth: '64ch', +} as const; + +const codeBlockStyle = { + fontFamily: tokens.typography.fontMono, + fontSize: 13, + lineHeight: 1.6, + background: tokens.surfaces.surfaceTinted, + border: `1px solid ${tokens.surfaces.border}`, + borderRadius: 8, + padding: 16, + overflow: 'auto', + color: tokens.colors.textPrimary, + margin: '0 0 16px', + whiteSpace: 'pre' as const, +} as const; + +const tableStyle = { + width: '100%', + borderCollapse: 'collapse' as const, + fontFamily: tokens.typography.body.family, + fontSize: 14, + color: tokens.colors.textSecondary, + margin: '0 0 24px', +} as const; + +const cellStyle = { + padding: '10px 12px', + borderBottom: `1px solid ${tokens.surfaces.border}`, + verticalAlign: 'top' as const, +} as const; + +const headerCellStyle = { + ...cellStyle, + color: tokens.colors.textPrimary, + fontWeight: 600, + background: tokens.surfaces.surfaceTinted, +} as const; + +export default function LicensingPage() { + return ( + <> +
+ +
+ Documentation +

+ Licensing +

+

+ How the ThreadPlane licensing model works, who needs a paid license, and how to install your license token. +

+
+
+
+ +
+ +
+

The model

+

+ Agent UI for Angular is a suite of libraries. Most are{' '} + MIT-licensed and free for any use, + commercial or not. Only @ngaf/chat is + dual-licensed. +

+

+ @ngaf/chat is source-available under{' '} + PolyForm Noncommercial 1.0.0 for free + noncommercial use, or a ThreadPlane Commercial license{' '} + for production use inside a for-profit context. The same source ships under both — you don't get a + different build. +

+ +

Do you need a paid license?

+

+ You need a ThreadPlane Commercial license if you use @ngaf/chat{' '} + in any of: +

+
    +
  • A commercial product or SaaS
  • +
  • An internal business tool inside a for-profit company
  • +
  • An agency deliverable or paid client project
  • +
  • Any application operated by or for a for-profit entity
  • +
+

You do not need a paid license for:

+
    +
  • Personal, hobby, student, academic, or nonprofit projects
  • +
  • Public demos and tutorials
  • +
  • Open-source applications released under an OSI-approved license
  • +
  • Commercial evaluation, up to 30 calendar days from your first commercial use
  • +
+
+
+
+ +
+ +
+

Install your license

+

+ After purchase, ThreadPlane emails a signed license token to the address on your receipt. Paste it + into your app's provideChat(){' '} + configuration: +

+
{`// app.config.ts
+import { ApplicationConfig } from '@angular/core';
+import { provideChat } from '@ngaf/chat';
+
+export const appConfig: ApplicationConfig = {
+  providers: [
+    provideChat({
+      license: process.env['THREADPLANE_LICENSE'],
+    }),
+  ],
+};`}
+

+ The library verifies the token's Ed25519 signature on boot. The check is{' '} + advisory-only: a missing, expired, or + tampered token logs a console.warn but + never blocks rendering. Verification is fully offline; no calls leave your app at runtime. +

+

+ The token is safe to commit to a private repository, or to read from a build-time environment variable + for public repos. Public-repo demos are exempt from the commercial-use definition, but if your + public repo backs a commercial product, the deployed bundle does need a license. +

+
+
+
+ +
+ +
+

Tier scoping

+

+ Pick the tier that matches how you'll deploy. All paid tiers grant the same{' '} + ThreadPlane Commercial license; the difference is the scope of use and the number of seats. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TierDevelopersCommercial appsBest for
Indie — $149/yr11Solo devs, single indie product
Developer Seat — $299/dev/yrPer seatUnlimited apps owned by your orgStartups, growing teams
App Deployment — $1,499/app/yrUnlimited1 production app per licenseAgencies, CI/CD-heavy teams
Enterprise — customCustomMulti-app, custom termsProcurement, SLA, security review
+
+

+ Every license is valid for 12 months from the date of purchase. Renewal is a fresh annual purchase — + we don't auto-charge. +

+
+
+
+ +
+ +
+

Evaluation

+

+ You may use @ngaf/chat commercially + for 30 calendar days from your first + commercial use as a good-faith evaluation. There is no telemetry, no registration, no email check — + we trust you to count the days. After 30 days you must either purchase a license or stop the + commercial use. +

+ +

Refunds

+

+ If you refund a license through Stripe, the token is revoked automatically and we email a confirmation. + The verification check warns on boot. There's no clawback of the source code you already have — + everything is source-available under PolyForm Noncommercial by default. +

+ +

Questions

+

+ Volume pricing, multi-app licensing, audit clauses, custom terms — any of those, reach out and we'll + work it out. +

+
+ + + + Pricing FAQ → + +
+
+
+
+ + ); +} diff --git a/apps/website/src/app/pricing/page.tsx b/apps/website/src/app/pricing/page.tsx index de07919f..52c3d478 100644 --- a/apps/website/src/app/pricing/page.tsx +++ b/apps/website/src/app/pricing/page.tsx @@ -13,7 +13,7 @@ import { createPageMetadata } from '../../lib/site-metadata'; export const metadata = createPageMetadata({ title: 'Pricing — Agent UI for Angular', description: - '@ngaf/chat is free for noncommercial use under PolyForm Noncommercial 1.0.0. Commercial production use requires a Threadplane license. Other libraries remain MIT.', + '@ngaf/chat is free for noncommercial use under PolyForm Noncommercial 1.0.0. Commercial production use requires a ThreadPlane Commercial license. Other libraries remain MIT.', pathname: '/pricing', type: 'website', }); @@ -67,7 +67,7 @@ export default function PricingPage() { margin: 0, }} > - @ngaf/chat is free for noncommercial use. Commercial production use requires a Threadplane license. Other libraries in the framework remain MIT. + @ngaf/chat is free for noncommercial use. Commercial production use requires a ThreadPlane Commercial license. Other libraries in the framework remain MIT.

@@ -126,7 +126,7 @@ export default function PricingPage() {
- Because commercial use requires a license, @ngaf/chat is source-available rather than OSI open source. Threadplane keeps ecosystem packages (@ngaf/render, @ngaf/agent, @ngaf/langgraph, @ngaf/ag-ui, @ngaf/a2ui, @ngaf/licensing, @ngaf/telemetry, @ngaf/design-tokens) permissively MIT-licensed. + Because commercial use requires a license, @ngaf/chat is source-available rather than OSI open source. ThreadPlane keeps ecosystem packages (@ngaf/render, @ngaf/agent, @ngaf/langgraph, @ngaf/ag-ui, @ngaf/a2ui, @ngaf/licensing, @ngaf/telemetry, @ngaf/design-tokens) permissively MIT-licensed.
diff --git a/apps/website/src/app/thanks/page.tsx b/apps/website/src/app/thanks/page.tsx index 1dd98470..cf2f5cb9 100644 --- a/apps/website/src/app/thanks/page.tsx +++ b/apps/website/src/app/thanks/page.tsx @@ -7,7 +7,7 @@ import { Button } from '../../components/ui/Button'; import { createPageMetadata } from '../../lib/site-metadata'; export const metadata = createPageMetadata({ - title: 'Payment received — Threadplane', + title: 'Payment received — ThreadPlane', description: 'Your @ngaf/chat license token will be emailed shortly.', pathname: '/thanks', type: 'website', @@ -57,8 +57,8 @@ export default function ThanksPage() { If you don't see the email within 10 minutes, check spam or contact us.

- )} - + ); })}
+ +

+ All paid tiers include the ThreadPlane Commercial license · One-time annual payment · 12-month validity +

); diff --git a/pricing/tiers.config.ts b/pricing/tiers.config.ts index 360dfd14..7e7b29d0 100644 --- a/pricing/tiers.config.ts +++ b/pricing/tiers.config.ts @@ -21,8 +21,13 @@ export interface TierConfig { /** USD cents. null for free / custom. */ readonly priceCents: number | null; readonly displayPrice: string; + /** Short suffix rendered inline after the price, e.g. "/dev/yr". */ readonly displayPeriod: string; + /** Subtitle under the price; replaces the standalone period gray subline. */ + readonly subtitle: string; readonly features: readonly string[]; + /** Short one-liner shown in its own row below the features. */ + readonly bestFor: string; /** false → community (npm), enterprise (sales). true → real Stripe product + price. */ readonly stripeBuyable: boolean; /** Highlighted card in the PricingGrid. */ @@ -36,17 +41,17 @@ export interface TierConfig { export const TIERS: readonly TierConfig[] = [ { slug: 'community', - name: 'Community / Noncommercial', + name: 'Community', priceCents: null, displayPrice: 'Free', - displayPeriod: 'forever', + displayPeriod: '', + subtitle: 'forever', features: [ - 'Personal, student, academic, nonprofit, demo', + 'Personal, OSS, demos', 'Source access', - 'Noncommercial use', - 'Commercial evaluation (30 days)', - 'License: PolyForm Noncommercial 1.0.0', + '30-day commercial eval', ], + bestFor: 'Tinkering, OSS projects, students', stripeBuyable: false, highlight: false, }, @@ -55,14 +60,14 @@ export const TIERS: readonly TierConfig[] = [ name: 'Indie', priceCents: 14900, displayPrice: '$149', - displayPeriod: '/year', + displayPeriod: '/yr', + subtitle: 'one app · one dev', features: [ '1 developer', '1 commercial app', 'Unlimited end users', - 'ThreadPlane Commercial license', - 'Best for: solo devs, indie products, consultants with one app', ], + bestFor: 'Solo founders, indie SaaS', stripeBuyable: true, highlight: false, }, @@ -71,14 +76,14 @@ export const TIERS: readonly TierConfig[] = [ name: 'Developer Seat', priceCents: 29900, displayPrice: '$299', - displayPeriod: '/developer/year', + displayPeriod: '/dev/yr', + subtitle: 'per developer', features: [ - 'Unlimited apps owned by your org', - 'Unlimited end users', - 'Dev / staging / production', - 'ThreadPlane Commercial license', - 'Best for: startups & growing teams', + 'Per developer seat', + 'Unlimited apps', + 'Dev · staging · prod', ], + bestFor: 'Startups, in-house teams', stripeBuyable: true, highlight: true, adjustableQuantity: true, @@ -89,14 +94,14 @@ export const TIERS: readonly TierConfig[] = [ name: 'App Deployment', priceCents: 149900, displayPrice: '$1,499', - displayPeriod: '/app/year', + displayPeriod: '/app/yr', + subtitle: 'per production app', features: [ 'Unlimited developers', '1 production app', - 'Unlimited end users', - 'ThreadPlane Commercial license', - 'Best for: agencies, CI/CD-heavy teams', + 'Procurement-friendly', ], + bestFor: 'Agencies, CI/CD-heavy teams', stripeBuyable: true, highlight: false, }, @@ -105,14 +110,14 @@ export const TIERS: readonly TierConfig[] = [ name: 'Enterprise', priceCents: null, displayPrice: 'Custom', - displayPeriod: 'annual', + displayPeriod: '', + subtitle: 'annual contract', features: [ - 'Custom contract & SLA', - 'Procurement support', - 'Security review', 'Multi-app licensing', - 'Priority + private support channel', + 'SLA + security review', + 'Private support channel', ], + bestFor: 'Procurement-led orgs', stripeBuyable: false, highlight: false, }, From 37ef8124af4890b7f9f591b4d8b66671545a9749 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 23 May 2026 07:56:31 -0700 Subject: [PATCH 03/15] feat(pricing): switch to full comparison-table layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Page-level changes: - H1 → "Simple, transparent pricing" (drop "Pricing for production AI chat interfaces" and the lede paragraph below) - Hero section uses tight vertical rhythm to pull the table closer - Remove the "A license is required when @ngaf/chat is used in a commercial product..." SmallNote — redundant with the table - Remove the redundant "Commercial evaluation is free for 30 days" note below the table — the table already shows a "30-day commercial eval" row - PricingGrid component removed from page (file kept for now in case the card layout is wanted elsewhere) CompareTable (rewritten to be the primary pricing display): - Header row: tier name + MOST POPULAR pill on Dev Seat - Sub-header row: price + period inline ($299 /dev/yr) - Feature rows pull all data from the same set we used in the grid plus License, Commercial production use, SLA, Security review - Footer row: per-column CTA button (outlined "secondary" for non-highlight tiers; solid "primary" for Dev Seat — more visually apparent than the prior ghost variant) - Dev Seat column tinted via tokens.surfaces.surfaceTinted - Disclaimer below table: "All paid tiers include the ThreadPlane Commercial license · One-time annual payment · 12-month validity" Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/website/src/app/pricing/page.tsx | 35 +- .../src/components/pricing/CompareTable.tsx | 420 ++++++++++++++---- 2 files changed, 347 insertions(+), 108 deletions(-) diff --git a/apps/website/src/app/pricing/page.tsx b/apps/website/src/app/pricing/page.tsx index 52c3d478..d51c197f 100644 --- a/apps/website/src/app/pricing/page.tsx +++ b/apps/website/src/app/pricing/page.tsx @@ -2,7 +2,6 @@ import { tokens } from '@ngaf/design-tokens'; import { Container } from '../../components/ui/Container'; import { Section } from '../../components/ui/Section'; import { Eyebrow } from '../../components/ui/Eyebrow'; -import { PricingGrid } from '../../components/pricing/PricingGrid'; import { CompareTable } from '../../components/pricing/CompareTable'; import { CompatibilityMatrix } from '../../components/pricing/CompatibilityMatrix'; import { PricingFAQ } from '../../components/pricing/PricingFAQ'; @@ -39,7 +38,7 @@ function SmallNote({ children }: { children: React.ReactNode }) { export default function PricingPage() { return ( <> -
+
Pricing @@ -52,47 +51,17 @@ export default function PricingPage() { lineHeight: tokens.typography.h1.line, color: tokens.colors.textPrimary, margin: 0, - marginBottom: 16, letterSpacing: '-0.02em', }} > - Pricing for production AI chat interfaces + Simple, transparent pricing -

- @ngaf/chat is free for noncommercial use. Commercial production use requires a ThreadPlane Commercial license. Other libraries in the framework remain MIT. -

- - -
- - - A license is required when @ngaf/chat is used in a commercial product, SaaS app, internal business tool, paid client project, or production application operated by or for a for-profit entity. - - -
- -
- - - Commercial evaluation is free for 30 days. A paid license is required before production deployment. - - -
-
Compatibility diff --git a/apps/website/src/components/pricing/CompareTable.tsx b/apps/website/src/components/pricing/CompareTable.tsx index a12417cc..f7c3ba34 100644 --- a/apps/website/src/components/pricing/CompareTable.tsx +++ b/apps/website/src/components/pricing/CompareTable.tsx @@ -1,126 +1,396 @@ 'use client'; + import { tokens } from '@ngaf/design-tokens'; +import { Button } from '../ui/Button'; +import { trackCtaClick } from '../../lib/analytics/client'; +import type { CtaId } from '../../lib/analytics/events'; +import { TIERS, type TierConfig } from '../../../../../pricing/tiers.config'; -type TierKey = 'community' | 'indie' | 'seat' | 'app' | 'enterprise'; +interface PlanCta { + readonly cta: string; + readonly ctaId: CtaId; + readonly stripeBuyable?: boolean; + readonly ctaHref?: string; + readonly ctaExternal?: boolean; +} -interface Row { +const CTAS: Record = { + community: { + cta: 'Start free', + ctaId: 'pricing_tier_community', + ctaHref: 'https://www.npmjs.com/package/@ngaf/chat', + ctaExternal: true, + }, + indie: { + cta: 'Buy Indie', + ctaId: 'pricing_tier_indie', + stripeBuyable: true, + }, + developer_seat: { + cta: 'Get Developer Seat', + ctaId: 'pricing_tier_developer_seat', + stripeBuyable: true, + }, + app_deployment: { + cta: 'License an App', + ctaId: 'pricing_tier_app_deployment', + stripeBuyable: true, + }, + enterprise: { + cta: 'Talk to Sales', + ctaId: 'pricing_tier_enterprise', + ctaHref: '/contact?source=pricing_tier_enterprise', + }, +}; + +type CellValue = boolean | string; +interface FeatureRow { feature: string; - cells: Record; + cells: Record; } -const TIERS: { key: TierKey; label: string }[] = [ - { key: 'community', label: 'Community' }, - { key: 'indie', label: 'Indie' }, - { key: 'seat', label: 'Developer Seat' }, - { key: 'app', label: 'App Deployment' }, - { key: 'enterprise', label: 'Enterprise' }, -]; - -const ROWS: Row[] = [ +const FEATURES: FeatureRow[] = [ { - feature: 'License model', + feature: 'License', cells: { - community: 'PolyForm NC 1.0.0', - indie: 'Commercial', - seat: 'Commercial', - app: 'Commercial', - enterprise: 'Commercial + custom', + community: 'PolyForm Noncommercial 1.0.0', + indie: 'ThreadPlane Commercial', + developer_seat: 'ThreadPlane Commercial', + app_deployment: 'ThreadPlane Commercial', + enterprise: 'ThreadPlane Commercial + custom', }, }, { feature: 'Commercial production use', - cells: { community: false, indie: true, seat: true, app: true, enterprise: true }, + cells: { community: false, indie: true, developer_seat: true, app_deployment: true, enterprise: true }, }, { feature: 'Developers', - cells: { community: 'Unlimited (noncommercial)', indie: '1', seat: 'Per seat', app: 'Unlimited', enterprise: 'Unlimited' }, + cells: { + community: 'Unlimited (noncommercial)', + indie: '1', + developer_seat: 'Per seat', + app_deployment: 'Unlimited', + enterprise: 'Unlimited', + }, }, { - feature: 'Apps covered', - cells: { community: 'Unlimited (noncommercial)', indie: '1', seat: 'All apps owned by your org', app: '1', enterprise: 'Multi-app' }, + feature: 'Commercial apps', + cells: { + community: '—', + indie: '1', + developer_seat: 'Unlimited (org-owned)', + app_deployment: '1', + enterprise: 'Multi-app', + }, }, { feature: 'End users', - cells: { community: 'Unlimited', indie: 'Unlimited', seat: 'Unlimited', app: 'Unlimited', enterprise: 'Unlimited' }, + cells: { community: 'Unlimited', indie: 'Unlimited', developer_seat: 'Unlimited', app_deployment: 'Unlimited', enterprise: 'Unlimited' }, + }, + { + feature: 'Dev · staging · prod', + cells: { community: false, indie: true, developer_seat: true, app_deployment: true, enterprise: true }, }, { - feature: 'Environments (dev / staging / prod)', - cells: { community: false, indie: true, seat: true, app: true, enterprise: true }, + feature: '30-day commercial eval', + cells: { community: true, indie: false, developer_seat: false, app_deployment: false, enterprise: false }, }, { feature: 'Support', - cells: { community: 'Community', indie: 'Email', seat: 'Email', app: 'Email', enterprise: 'Priority + private channel' }, + cells: { community: 'Community', indie: 'Email', developer_seat: 'Email', app_deployment: 'Email', enterprise: 'Priority + private channel' }, }, { feature: 'SLA', - cells: { community: false, indie: false, seat: false, app: false, enterprise: true }, + cells: { community: false, indie: false, developer_seat: false, app_deployment: false, enterprise: true }, }, { feature: 'Security review', - cells: { community: false, indie: false, seat: false, app: false, enterprise: true }, + cells: { community: false, indie: false, developer_seat: false, app_deployment: false, enterprise: true }, }, ]; -const Check = () => ; -const X = () => ; +const Check = () => ( + +); +const Dash = () => ( + +); + +function renderCell(value: CellValue): React.ReactNode { + if (typeof value === 'boolean') return value ? : ; + if (value === '—') return ; + return {value}; +} -function renderCell(value: boolean | string): React.ReactNode { - if (typeof value === 'boolean') return value ? : ; - return {value}; +function PlanButton({ tier }: { tier: TierConfig }) { + const cta = CTAS[tier.slug]; + const variant = tier.highlight ? 'primary' : 'secondary'; + const common = { + variant, + size: 'md' as const, + style: { width: '100%' }, + }; + if (cta.stripeBuyable) { + return ( + + + + + ); + } + return ( + + ); } export function CompareTable() { return ( -
-
- - - - - {TIERS.map((t) => ( +
+
+
+
- Feature -
+ + + {TIERS.map((tier) => { + const isHighlight = tier.highlight; + return ( + + ); + })} + + + + {TIERS.map((tier) => ( + + ))} + + + + {FEATURES.map((row, i) => ( + + + {TIERS.map((tier) => ( + + ))} + ))} - - - - {ROWS.map((row) => ( - (e.currentTarget.style.background = tokens.colors.accentSurface)} - onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')} - > - - {TIERS.map((t) => ( - + + + ))} - ))} - -
- {t.label} + Plan + {isHighlight && ( +
+ MOST POPULAR +
+ )} +
+ {tier.name} +
+
+ Price + +
+ + {tier.displayPrice} + + {tier.displayPeriod && ( + + {tier.displayPeriod} + + )} +
+
+ {row.feature} + + {renderCell(row.cells[tier.slug])} +
- {row.feature} - - {renderCell(row.cells[t.key])} +
+ {TIERS.map((tier) => ( + +
+ + +
+

+ All paid tiers include the ThreadPlane Commercial license · One-time annual payment · 12-month validity +

); } From 542047d1b85fc579b050ca63ca6dd0878c1c4843 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 23 May 2026 08:03:56 -0700 Subject: [PATCH 04/15] docs(licensing): drop historical-MIT + source-available footnote - Remove "What happens to older MIT versions?" from the pricing FAQ - Remove the bottom-of-pricing "Because commercial use requires a license, @ngaf/chat is source-available rather than OSI open source..." footnote - libs/chat/CHANGELOG.md: present-tense license entry; drop the historical MIT grandfathering line - PricingFAQ.spec: question count drops to 6; assertion now reads the question span (the shared FAQ component renders a chevron next to it which the old textContent assertion didn't account for) Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/website/src/app/pricing/page.tsx | 26 ------------------- .../components/pricing/PricingFAQ.spec.tsx | 9 +++---- .../src/components/pricing/PricingFAQ.tsx | 4 --- libs/chat/CHANGELOG.md | 4 +-- 4 files changed, 6 insertions(+), 37 deletions(-) diff --git a/apps/website/src/app/pricing/page.tsx b/apps/website/src/app/pricing/page.tsx index d51c197f..823c7ddd 100644 --- a/apps/website/src/app/pricing/page.tsx +++ b/apps/website/src/app/pricing/page.tsx @@ -17,24 +17,6 @@ export const metadata = createPageMetadata({ type: 'website', }); -function SmallNote({ children }: { children: React.ReactNode }) { - return ( -

- {children} -

- ); -} - export default function PricingPage() { return ( <> @@ -92,14 +74,6 @@ export default function PricingPage() { -
- - - Because commercial use requires a license, @ngaf/chat is source-available rather than OSI open source. ThreadPlane keeps ecosystem packages (@ngaf/render, @ngaf/agent, @ngaf/langgraph, @ngaf/ag-ui, @ngaf/a2ui, @ngaf/licensing, @ngaf/telemetry, @ngaf/design-tokens) permissively MIT-licensed. - - -
- diff --git a/apps/website/src/components/pricing/PricingFAQ.spec.tsx b/apps/website/src/components/pricing/PricingFAQ.spec.tsx index cd4ba263..b630f89d 100644 --- a/apps/website/src/components/pricing/PricingFAQ.spec.tsx +++ b/apps/website/src/components/pricing/PricingFAQ.spec.tsx @@ -21,22 +21,21 @@ const EXPECTED_QUESTIONS = [ 'Do my end users need licenses?', 'Can I modify the source?', 'Can I redistribute it?', - 'What happens to older MIT versions?', ]; describe('PricingFAQ', () => { it('renders the FAQ heading', () => { render(); expect( - screen.getByRole('heading', { level: 2, name: 'Licensing FAQ' }), + screen.getByRole('heading', { level: 2, name: /Licensing FAQ/ }), ).toBeTruthy(); }); - it('renders all 7 questions as elements inside
', () => { + it('renders all questions as elements inside
', () => { const { container } = render(); const summaries = container.querySelectorAll('details > summary'); - expect(summaries.length).toBe(7); - const texts = Array.from(summaries, (s) => s.textContent); + expect(summaries.length).toBe(EXPECTED_QUESTIONS.length); + const texts = Array.from(summaries, (s) => s.querySelector('span')?.textContent?.trim()); expect(texts).toEqual(EXPECTED_QUESTIONS); }); diff --git a/apps/website/src/components/pricing/PricingFAQ.tsx b/apps/website/src/components/pricing/PricingFAQ.tsx index 14baecdb..f76f3286 100644 --- a/apps/website/src/components/pricing/PricingFAQ.tsx +++ b/apps/website/src/components/pricing/PricingFAQ.tsx @@ -29,10 +29,6 @@ const ITEMS: FAQItem[] = [ q: 'Can I redistribute it?', a: 'You may bundle it inside a larger licensed application. You may not redistribute it as a standalone package or as part of a competing component library, SDK, template kit, app builder, or design system.', }, - { - q: 'What happens to older MIT versions?', - a: 'Versions previously released under MIT remain available under their original license terms. The new license applies only to future versions where the license change is introduced.', - }, ]; export function PricingFAQ() { diff --git a/libs/chat/CHANGELOG.md b/libs/chat/CHANGELOG.md index de575956..c54d779a 100644 --- a/libs/chat/CHANGELOG.md +++ b/libs/chat/CHANGELOG.md @@ -4,8 +4,8 @@ ### Changed -- **License:** Changed the license for `@ngaf/chat` from MIT to PolyForm Noncommercial 1.0.0 plus commercial licensing. This change applies to future versions only. Historical MIT releases remain under their original license terms. +- **License:** `@ngaf/chat` is dual-licensed under PolyForm Noncommercial 1.0.0 (free noncommercial use) or a ThreadPlane Commercial license (production use inside a for-profit context). ### Migration -Commercial users upgrading to this version or later need a Threadplane commercial license before production deployment. See [COMMERCIAL-USE.md](./COMMERCIAL-USE.md) for the definition of commercial use and the 30-day evaluation window, and for plans. +Commercial users need a ThreadPlane Commercial license before production deployment. See [COMMERCIAL-USE.md](./COMMERCIAL-USE.md) for the definition of commercial use and the 30-day evaluation window, and for plans. From 86826ee3561ad9e4a5f9d66c273bdfff27d7b302 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 23 May 2026 08:07:12 -0700 Subject: [PATCH 05/15] feat(pricing): enterprise lead form bridges Pilot-to-Prod into Enterprise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lead form (value-prop + qualifying form direction): - Headline: "Built for procurement. / Backed by delivery." - Subtitle: "Volume licensing, custom contract, and optional concierge delivery — so your first Angular agent ships, not just compiles." - Side-by-side layout at wide widths: value props on the left, form on the right. Stacks vertically on narrow widths. - Four value-prop tiles. The Pilot-to-Prod tile is highlighted with the accent border + tinted background, plus a deep link to /pilot-to-prod. - Form gains qualifying fields: - Team size (select: 1-5 / 6-25 / 26-100 / 100+ developers) - Timeline (select: This quarter / Next quarter / 6+ months / exploring) - Pilot-to-Prod interest (radio: Yes, include it / Tell me more / License only) — sent as `pilot_interest` in the lead payload so sales can route accordingly - Company is now required (was optional) — enterprise leads always have a company - CTA: "Request enterprise quote" (was "Get in touch") Compare table: new "Pilot-to-Prod engagement" row, marked "Optional" for Enterprise and "—" for everything else. Makes the bridge visible without having to scroll to the lead form. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/pricing/CompareTable.tsx | 4 + .../src/components/pricing/LeadForm.tsx | 401 ++++++++++++++---- 2 files changed, 321 insertions(+), 84 deletions(-) diff --git a/apps/website/src/components/pricing/CompareTable.tsx b/apps/website/src/components/pricing/CompareTable.tsx index f7c3ba34..65a788f8 100644 --- a/apps/website/src/components/pricing/CompareTable.tsx +++ b/apps/website/src/components/pricing/CompareTable.tsx @@ -108,6 +108,10 @@ const FEATURES: FeatureRow[] = [ feature: 'Security review', cells: { community: false, indie: false, developer_seat: false, app_deployment: false, enterprise: true }, }, + { + feature: 'Pilot-to-Prod engagement', + cells: { community: false, indie: false, developer_seat: false, app_deployment: false, enterprise: 'Optional' }, + }, ]; const Check = () => ( diff --git a/apps/website/src/components/pricing/LeadForm.tsx b/apps/website/src/components/pricing/LeadForm.tsx index ea604aae..1e23d715 100644 --- a/apps/website/src/components/pricing/LeadForm.tsx +++ b/apps/website/src/components/pricing/LeadForm.tsx @@ -9,8 +9,30 @@ import { Eyebrow } from '../ui/Eyebrow'; import { Button } from '../ui/Button'; import { Card } from '../ui/Card'; +const VALUE_PROPS = [ + { + title: 'ThreadPlane Commercial license', + body: 'Multi-app coverage, unlimited developers, custom contract — built for procurement.', + }, + { + title: 'SLA + security review', + body: 'Response SLAs, security questionnaires, and a private support channel.', + }, + { + title: 'Pilot-to-Prod engagement', + body: '8-week concierge delivery. We ship your first Angular agent on your real data, in your real app — and your engineers own it at the end.', + highlight: true, + link: { href: '/pilot-to-prod', label: 'See how Pilot-to-Prod works →' }, + }, + { + title: 'Procurement support', + body: 'Master services agreement, security review, custom indemnification — handled by humans, not portals.', + }, +]; + export function LeadForm() { const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle'); + const [pilotInterest, setPilotInterest] = useState<'yes' | 'maybe' | 'no'>('maybe'); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -25,7 +47,7 @@ export function LeadForm() { const res = await fetch('/api/leads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), + body: JSON.stringify({ ...data, pilot_interest: pilotInterest }), }); if (res.ok) { track(analyticsEvents.marketingLeadFormSuccess, { @@ -63,11 +85,11 @@ export function LeadForm() { outline: 'none', }; - const handleFocus = (e: React.FocusEvent) => { + const handleFocus = (e: React.FocusEvent) => { e.target.style.borderColor = tokens.colors.accent; e.target.style.boxShadow = tokens.shadows.focus; }; - const handleBlur = (e: React.FocusEvent) => { + const handleBlur = (e: React.FocusEvent) => { e.target.style.borderColor = tokens.surfaces.border; e.target.style.boxShadow = 'none'; }; @@ -75,91 +97,302 @@ export function LeadForm() { return (
-
- Enterprise -

+
+ Enterprise +

+ Built for procurement.
Backed by delivery. +

+

+ Volume licensing, custom contract, and optional concierge delivery — so your first Angular agent ships, not just compiles. +

+
+ +
- Need volume seats or a custom contract? -

- {status === 'sent' ? ( -

- Thanks — we'll be in touch within one business day. -

- ) : ( - -
- - - - - - - -