+
+ Documentation
+
Licensing
-
+
How the Threadplane licensing model works, who needs a paid license, and how to install your license token.
@@ -121,35 +32,35 @@ export default function LicensingPage() {
-
- The model
-
+
+ The model
+
Threadplane is a suite of libraries. Most are{' '}
- MIT-licensed and free for any use,
- commercial or not. Only @threadplane/chat is
+ MIT-licensed and free for any use,
+ commercial or not. Only @threadplane/chat is
dual-licensed.
-
- @threadplane/chat is source-available under{' '}
- PolyForm Noncommercial 1.0.0 for free
- noncommercial use, or a Threadplane Commercial license{' '}
+
+ @threadplane/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 @threadplane/chat{' '}
+
Do you need a paid license?
+
+ You need a Threadplane Commercial license if you use @threadplane/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:
-
+ 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
@@ -161,14 +72,14 @@ export default function LicensingPage() {
-
- Install your license
-
+
+ Install your license
+
After purchase, Threadplane emails a signed license token to the address on your receipt. Paste it
- into your app's provideChat(){' '}
+ into your app's provideChat(){' '}
configuration:
- {`// app.config.ts
+ {`// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideChat } from '@threadplane/chat';
@@ -179,13 +90,13 @@ export const appConfig: ApplicationConfig = {
}),
],
};`}
-
+
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
+ 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.
@@ -196,41 +107,41 @@ export const appConfig: ApplicationConfig = {
-
- Tier scoping
-
+
+ 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.
-
-
+
+
- Tier
- Developers
- Best for
+ Tier
+ Developers
+ Best for
- Developer Seat — $29/dev/mo or $299/dev/yr
- Per seat
- Solo devs, growing teams
+ Developer Seat — $29/dev/mo or $299/dev/yr
+ Per seat
+ Solo devs, growing teams
- Team — $149/mo or $1,495/yr
- 5 seats included
- Small teams that want a single SKU and renewal
+ Team — $149/mo or $1,495/yr
+ 5 seats included
+ Small teams that want a single SKU and renewal
- Enterprise — from $4,000/mo
- Custom
- SLA, security review, Pilot-to-Prod engagement, Slack Connect
+ Enterprise — from $4,000/mo
+ Custom
+ SLA, security review, Pilot-to-Prod engagement, Slack Connect
-
+
Paid tiers are recurring subscriptions. Annual saves ~15% vs monthly. Cancel anytime — the license
stays valid through the end of the current paid period.
@@ -240,47 +151,36 @@ export const appConfig: ApplicationConfig = {
-
- Evaluation
-
- You may use @threadplane/chat commercially
- for 30 calendar days from your first
+
+ Evaluation
+
+ You may use @threadplane/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
-
+
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
-
+
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/docs/page.tsx b/apps/website/src/app/docs/page.tsx
index 67a764ef4..066baa729 100644
--- a/apps/website/src/app/docs/page.tsx
+++ b/apps/website/src/app/docs/page.tsx
@@ -1,6 +1,5 @@
import type { ReactNode } from 'react';
import Link from 'next/link';
-import { tokens } from '@threadplane/design-tokens';
import { Container } from '../../components/ui/Container';
import { Section } from '../../components/ui/Section';
import { Eyebrow } from '../../components/ui/Eyebrow';
@@ -136,181 +135,36 @@ function MiddlewareGlyph() {
const GLYPHS = { key: KeyGlyph, middleware: MiddlewareGlyph, pulse: PulseGlyph } as const;
-const stepLabelStyle = {
- fontFamily: tokens.typography.eyebrow.family,
- fontSize: tokens.typography.eyebrow.size,
- fontWeight: tokens.typography.eyebrow.weight,
- letterSpacing: tokens.typography.eyebrow.letterSpacing,
- textTransform: tokens.typography.eyebrow.transform,
- lineHeight: tokens.typography.eyebrow.line,
- color: tokens.colors.textMuted,
- margin: 0,
- marginBottom: 16,
- display: 'flex',
- alignItems: 'center',
- gap: 10,
-} as const;
-
-const stepBadgeStyle = {
- width: 20,
- height: 20,
- borderRadius: tokens.radius.full,
- background: tokens.colors.accent,
- color: '#fff',
- fontSize: 12,
- fontWeight: 700,
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- flex: '0 0 auto',
-} as const;
-
function StepLabel({ id, step, children }: { id: string; step?: number; children: ReactNode }) {
return (
-
+
{step != null ? (
-
+
) : null}
{children}
);
}
-const logoChipStyle = {
- width: 30,
- height: 30,
- borderRadius: tokens.radius.md,
- background: tokens.surfaces.surface,
- border: `1px solid ${tokens.surfaces.border}`,
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- flex: '0 0 auto',
-} as const;
-
-const glyphChipStyle = {
- borderRadius: tokens.radius.md,
- background: tokens.colors.accentSurface,
- border: `1px solid ${tokens.colors.accentBorder}`,
- color: tokens.colors.accent,
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- flex: '0 0 auto',
-} as const;
-
function LogoChip({ src }: { src: string }) {
return (
-
+
);
}
function GlyphChip({ size, children }: { size: number; children: ReactNode }) {
- return {children};
+ return {children};
}
-const cardHeaderStyle = {
- display: 'flex',
- alignItems: 'center',
- gap: 12,
- marginBottom: 12,
-} as const;
-
-const cardTitleStyle = {
- fontFamily: tokens.typography.h3.family,
- fontSize: 18,
- lineHeight: 1.3,
- fontWeight: 600,
- color: tokens.colors.textPrimary,
- margin: 0,
-} as const;
-
-const attributionStyle = {
- fontFamily: tokens.typography.fontMono,
- fontSize: 10,
- lineHeight: 1.4,
- textTransform: 'uppercase',
- letterSpacing: '0.08em',
- color: tokens.colors.textMuted,
- marginTop: 2,
-} as const;
-
-const cardBlurbStyle = {
- fontFamily: tokens.typography.body.family,
- fontSize: tokens.typography.body.size,
- lineHeight: tokens.typography.body.line,
- color: tokens.colors.textSecondary,
- margin: 0,
-} as const;
-
-const ctaStyle = {
- fontFamily: tokens.typography.fontSans,
- fontSize: 14,
- fontWeight: 600,
- color: tokens.colors.accent,
-} as const;
-
-const snippetRowStyle = {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- gap: 8,
- background: tokens.surfaces.surface,
- border: `1px solid ${tokens.surfaces.border}`,
- borderRadius: tokens.radius.md,
- padding: '5px 6px 5px 12px',
- margin: '14px 0 16px',
-} as const;
-
-const snippetCodeStyle = {
- fontFamily: tokens.typography.fontMono,
- fontSize: 13,
- color: tokens.colors.textSecondary,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
-} as const;
-
-const gridStyle = {
- display: 'grid',
- gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
- gap: 16,
-} as const;
-
-const helperStyle = {
- fontFamily: tokens.typography.body.family,
- fontSize: 14,
- color: tokens.colors.textSecondary,
- margin: 0,
- marginTop: 16,
- textAlign: 'center',
-} as const;
-
-const helperLinkStyle = {
- color: tokens.colors.accent,
- fontWeight: 600,
-} as const;
-
-const fillHeightStyle = {
- height: '100%',
-} as const;
-
-const dividerStyle = {
- height: 1,
- background: tokens.surfaces.border,
- border: 'none',
- margin: '0 0 40px',
-} as const;
-
export default function DocsLandingPage() {
return (
<>
@@ -318,35 +172,14 @@ export default function DocsLandingPage() {
{/* Hero */}
-
-
+
+
Documentation
-
+
Start building with Threadplane
-
+
Streaming agent interfaces with runtime adapters, a shared Agent contract,
and a drop-in chat surface. Most packages are MIT; @threadplane/chat is
dual-licensed for noncommercial/evaluation and commercial production use.
@@ -359,34 +192,34 @@ export default function DocsLandingPage() {
Pick your backend
-
+
{BACKENDS.map((b) => (
-
-
-
+
+
+
- {b.title}
- {b.attribution}
+ {b.title}
+ {b.attribution}
- {b.blurb}
-
- {b.install}
+ {b.blurb}
+
+ {b.install}
- Adapter quickstart →
+ Adapter quickstart →
))}
-
+
Not sure which to use?{' '}
-
+
Choosing an adapter →
{' '}Want the drop-in UI first?{' '}
-
+
Chat quickstart →
@@ -396,28 +229,28 @@ export default function DocsLandingPage() {
{/* Step 2 — generative UI */}
-
+
Generative UI
-
+
{GENERATIVE_UI.map((g) => (
-
-
-
+
+
+
- {g.title}
- {g.attribution}
+ {g.title}
+ {g.attribution}
- {g.blurb}
- Get started →
+ {g.blurb}
+ Get started →
))}
-
+
Which fits my use case?{' '}
-
+
json-render vs A2UI →
@@ -427,18 +260,18 @@ export default function DocsLandingPage() {
{/* Step 3 — chat */}
-
+
Chat UI
-
-
-
+
+
+
- Chat
- Threadplane
+ Chat
+ Threadplane
-
+
Drop-in chat components — message list, input, streaming, tool
calls, interrupts, subagents. Renders A2UI & json-render surfaces
inline.
@@ -451,19 +284,19 @@ export default function DocsLandingPage() {
{/* Supporting libraries */}
-
+
Supporting libraries
-
+
{SUPPORTING.map((s) => {
const Glyph = GLYPHS[s.glyph];
return (
-
-
-
+
+
+
- {s.title}
- {s.blurb}
+ {s.title}
+ {s.blurb}
@@ -477,33 +310,11 @@ export default function DocsLandingPage() {
{/* Search prompt */}
-
-
+
+
Looking for something specific?
-
+
Press ⌘K to search the docs.
diff --git a/apps/website/src/styles/pages.css b/apps/website/src/styles/pages.css
index 55730ebf4..df170a587 100644
--- a/apps/website/src/styles/pages.css
+++ b/apps/website/src/styles/pages.css
@@ -8,3 +8,364 @@
*
* Migration: docs/superpowers/plans/2026-08-29-inline-style-substrate-migration.md
*/
+
+/* Licensing page — app/docs/licensing/page.tsx */
+.licensing-hero-inner {
+ max-width: 720px;
+}
+.licensing-eyebrow-spaced {
+ margin-bottom: 16px;
+}
+.licensing-h1 {
+ font-family: var(--font-garamond);
+ font-size: var(--text-h1);
+ line-height: var(--text-h1--line-height);
+ font-weight: 700;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 16px;
+ letter-spacing: -0.02em;
+}
+.licensing-subtitle {
+ font-family: var(--font-inter);
+ font-size: var(--text-body-lg);
+ line-height: var(--text-body-lg--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+ max-width: 60ch;
+}
+.licensing-section-inner {
+ max-width: 760px;
+}
+.licensing-section-inner-wide {
+ max-width: 900px;
+}
+.licensing-h2 {
+ font-family: var(--font-garamond);
+ font-size: var(--text-h2);
+ line-height: var(--text-h2--line-height);
+ font-weight: 700;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 16px;
+ letter-spacing: -0.01em;
+}
+.licensing-h2-spaced {
+ margin-top: 32px;
+}
+.licensing-h3 {
+ font-family: var(--font-inter);
+ font-size: var(--text-h3);
+ line-height: var(--text-h3--line-height);
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-top: 24px;
+ margin-bottom: 8px;
+}
+.licensing-body {
+ font-family: var(--font-inter);
+ font-size: var(--text-body);
+ line-height: var(--text-body--line-height);
+ color: var(--color-text-secondary);
+ margin: 0 0 16px;
+ max-width: 64ch;
+}
+.licensing-list {
+ padding-left: 24px;
+}
+.licensing-strong {
+ color: var(--color-text-primary);
+}
+.licensing-code-inline {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: "JetBrains Mono", monospace;
+}
+.licensing-code-block {
+ font-family: "JetBrains Mono", monospace;
+ font-size: 13px;
+ line-height: 1.6;
+ background: var(--color-surface-tinted);
+ border: 1px solid var(--color-border);
+ border-radius: 8px;
+ padding: 16px;
+ overflow: auto;
+ color: var(--color-text-primary);
+ margin: 0 0 16px;
+ white-space: pre;
+}
+.licensing-table-scroll {
+ overflow: auto;
+}
+.licensing-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-family: var(--font-inter);
+ font-size: 14px;
+ color: var(--color-text-secondary);
+ margin: 0 0 24px;
+}
+.licensing-cell {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--color-border);
+ vertical-align: top;
+}
+.licensing-header-cell {
+ color: var(--color-text-primary);
+ font-weight: 600;
+ background: var(--color-surface-tinted);
+}
+.licensing-footnote {
+ font-size: 13px;
+ color: var(--color-text-muted);
+}
+.licensing-cta-row {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-top: 16px;
+}
+.licensing-faq-link {
+ display: inline-flex;
+ align-items: center;
+ font-family: var(--font-inter);
+ font-size: 14px;
+ color: var(--color-accent);
+ text-decoration: none;
+ padding: 8px 12px;
+}
+
+/* Docs index page — app/docs/page.tsx */
+.docs-index-hero-inner {
+ max-width: 720px;
+}
+.docs-index-eyebrow-spaced {
+ margin-bottom: 16px;
+}
+.docs-index-h1 {
+ font-family: var(--font-garamond);
+ font-size: var(--text-h1);
+ line-height: var(--text-h1--line-height);
+ font-weight: 700;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 16px;
+ letter-spacing: -0.02em;
+}
+.docs-index-subtitle {
+ font-family: var(--font-inter);
+ font-size: var(--text-body-lg);
+ line-height: var(--text-body-lg--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+ max-width: 52ch;
+}
+.docs-index-step-label {
+ font-family: var(--font-mono);
+ font-size: var(--text-eyebrow);
+ font-weight: var(--text-eyebrow--font-weight);
+ letter-spacing: var(--text-eyebrow--letter-spacing);
+ text-transform: uppercase;
+ line-height: var(--text-eyebrow--line-height);
+ color: var(--color-text-muted);
+ margin: 0;
+ margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.docs-index-step-badge {
+ width: 20px;
+ height: 20px;
+ border-radius: var(--radius-full);
+ background: var(--color-accent);
+ color: #fff;
+ font-size: 12px;
+ font-weight: 700;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+}
+.docs-index-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
+}
+.docs-index-card-link {
+ text-decoration: none;
+}
+.docs-index-fill-height {
+ height: 100%;
+}
+.docs-index-card-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+/* Supporting-libraries card header omits the bottom margin in the source —
+ * a separate class rather than a shared one plus a zeroing modifier, since
+ * "cancel a shorthand's margin-bottom back to nothing" is not otherwise
+ * needed anywhere else in this file. */
+.docs-index-card-header-inline {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+.docs-index-logo-chip {
+ width: 30px;
+ height: 30px;
+ border-radius: var(--radius-md);
+ background: var(--color-surface);
+ border: 1px solid var(--color-border);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+}
+.docs-index-logo-img {
+ width: 18px;
+ height: 18px;
+ object-fit: contain;
+}
+/* GlyphChip's width/height come from a `size: number` prop (unbounded) and
+ * stay inline; everything else in glyphChipStyle moves here. */
+.docs-index-glyph-chip {
+ border-radius: var(--radius-md);
+ background: var(--color-accent-surface);
+ border: 1px solid var(--color-accent-border);
+ color: var(--color-accent);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+}
+.docs-index-card-title {
+ font-family: var(--font-inter);
+ font-size: 18px;
+ line-height: 1.3;
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+}
+.docs-index-card-title-sm {
+ font-size: 16px;
+}
+.docs-index-attribution {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: "JetBrains Mono", monospace;
+ font-size: 10px;
+ line-height: 1.4;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--color-text-muted);
+ margin-top: 2px;
+}
+.docs-index-card-blurb {
+ font-family: var(--font-inter);
+ font-size: var(--text-body);
+ line-height: var(--text-body--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+}
+.docs-index-card-blurb-sm {
+ font-size: 13px;
+ margin-top: 2px;
+}
+.docs-index-cta {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: Inter, system-ui, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--color-accent);
+}
+.docs-index-cta-block {
+ display: inline-block;
+ margin-top: 14px;
+}
+.docs-index-snippet-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ background: var(--color-surface);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ padding: 5px 6px 5px 12px;
+ margin: 14px 0 16px;
+}
+.docs-index-snippet-code {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: "JetBrains Mono", monospace;
+ font-size: 13px;
+ color: var(--color-text-secondary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.docs-index-helper {
+ font-family: var(--font-inter);
+ font-size: 14px;
+ color: var(--color-text-secondary);
+ margin: 0;
+ margin-top: 16px;
+ text-align: center;
+}
+.docs-index-helper-link {
+ color: var(--color-accent);
+ font-weight: 600;
+}
+.docs-index-divider {
+ height: 1px;
+ background: var(--color-border);
+ border: none;
+ margin: 0 0 40px;
+}
+.docs-index-search-inner {
+ text-align: center;
+ max-width: 640px;
+ margin: 0 auto;
+}
+.docs-index-search-heading {
+ font-family: var(--font-inter);
+ font-size: 22px;
+ line-height: 1.3;
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 12px;
+}
+.docs-index-search-copy {
+ font-family: var(--font-inter);
+ font-size: var(--text-body);
+ line-height: var(--text-body--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* Choosing-an-adapter page — app/docs/choosing-an-adapter/page.tsx */
+.adapter-hero-inner {
+ max-width: 720px;
+}
+.adapter-eyebrow-spaced {
+ margin-bottom: 16px;
+}
+.adapter-article {
+ max-width: 760px;
+}
+
+/* Docs shell — app/docs/[library]/[section]/[slug]/page.tsx
+ * paddingTop: 80 and the overflow-x-hidden utility are known defects
+ * (nav-height coupling) migrated VERBATIM here; a later project fixes them,
+ * not this migration. */
+.docs-shell-page {
+ background: var(--color-canvas);
+ padding-top: 80px;
+}
+.docs-shell-body {
+ background: var(--color-surface);
+}
Licensing
-+
How the Threadplane licensing model works, who needs a paid license, and how to install your license token.
The model
-+
The model
+
Threadplane is a suite of libraries. Most are{' '}
- MIT-licensed and free for any use,
- commercial or not. Only @threadplane/chat is
+ MIT-licensed and free for any use,
+ commercial or not. Only @threadplane/chat is
dual-licensed.
- @threadplane/chat is source-available under{' '}
- PolyForm Noncommercial 1.0.0 for free
- noncommercial use, or a Threadplane Commercial license{' '}
+
+ @threadplane/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 @threadplane/chat{' '}
+
Do you need a paid license?
+
+ You need a Threadplane Commercial license if you use @threadplane/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
- Personal, hobby, student, academic, or nonprofit projects
- Public demos and tutorials
- Open-source applications released under an OSI-approved license @@ -161,14 +72,14 @@ export default function LicensingPage() {
You do not need a paid license for:
--
+
You do not need a paid license for:
+Install your license
-+
Install your license
+
After purchase, Threadplane emails a signed license token to the address on your receipt. Paste it
- into your app's provideChat(){' '}
+ into your app's provideChat(){' '}
configuration:
{`// app.config.ts
+ {`// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideChat } from '@threadplane/chat';
@@ -179,13 +90,13 @@ export const appConfig: ApplicationConfig = {
}),
],
};`}
-
+
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
+ 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.
@@ -196,41 +107,41 @@ export const appConfig: ApplicationConfig = {
-
- Tier scoping
-
+
+ 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.
-
-
+
+
- Tier
- Developers
- Best for
+ Tier
+ Developers
+ Best for
- Developer Seat — $29/dev/mo or $299/dev/yr
- Per seat
- Solo devs, growing teams
+ Developer Seat — $29/dev/mo or $299/dev/yr
+ Per seat
+ Solo devs, growing teams
- Team — $149/mo or $1,495/yr
- 5 seats included
- Small teams that want a single SKU and renewal
+ Team — $149/mo or $1,495/yr
+ 5 seats included
+ Small teams that want a single SKU and renewal
- Enterprise — from $4,000/mo
- Custom
- SLA, security review, Pilot-to-Prod engagement, Slack Connect
+ Enterprise — from $4,000/mo
+ Custom
+ SLA, security review, Pilot-to-Prod engagement, Slack Connect
-
+
Paid tiers are recurring subscriptions. Annual saves ~15% vs monthly. Cancel anytime — the license
stays valid through the end of the current paid period.
@@ -240,47 +151,36 @@ export const appConfig: ApplicationConfig = {
-
- Evaluation
-
- You may use @threadplane/chat commercially
- for 30 calendar days from your first
+
+ Evaluation
+
+ You may use @threadplane/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
-
+
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
-
+
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/docs/page.tsx b/apps/website/src/app/docs/page.tsx
index 67a764ef4..066baa729 100644
--- a/apps/website/src/app/docs/page.tsx
+++ b/apps/website/src/app/docs/page.tsx
@@ -1,6 +1,5 @@
import type { ReactNode } from 'react';
import Link from 'next/link';
-import { tokens } from '@threadplane/design-tokens';
import { Container } from '../../components/ui/Container';
import { Section } from '../../components/ui/Section';
import { Eyebrow } from '../../components/ui/Eyebrow';
@@ -136,181 +135,36 @@ function MiddlewareGlyph() {
const GLYPHS = { key: KeyGlyph, middleware: MiddlewareGlyph, pulse: PulseGlyph } as const;
-const stepLabelStyle = {
- fontFamily: tokens.typography.eyebrow.family,
- fontSize: tokens.typography.eyebrow.size,
- fontWeight: tokens.typography.eyebrow.weight,
- letterSpacing: tokens.typography.eyebrow.letterSpacing,
- textTransform: tokens.typography.eyebrow.transform,
- lineHeight: tokens.typography.eyebrow.line,
- color: tokens.colors.textMuted,
- margin: 0,
- marginBottom: 16,
- display: 'flex',
- alignItems: 'center',
- gap: 10,
-} as const;
-
-const stepBadgeStyle = {
- width: 20,
- height: 20,
- borderRadius: tokens.radius.full,
- background: tokens.colors.accent,
- color: '#fff',
- fontSize: 12,
- fontWeight: 700,
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- flex: '0 0 auto',
-} as const;
-
function StepLabel({ id, step, children }: { id: string; step?: number; children: ReactNode }) {
return (
-
+
{step != null ? (
-
+
) : null}
{children}
);
}
-const logoChipStyle = {
- width: 30,
- height: 30,
- borderRadius: tokens.radius.md,
- background: tokens.surfaces.surface,
- border: `1px solid ${tokens.surfaces.border}`,
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- flex: '0 0 auto',
-} as const;
-
-const glyphChipStyle = {
- borderRadius: tokens.radius.md,
- background: tokens.colors.accentSurface,
- border: `1px solid ${tokens.colors.accentBorder}`,
- color: tokens.colors.accent,
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- flex: '0 0 auto',
-} as const;
-
function LogoChip({ src }: { src: string }) {
return (
-
+
);
}
function GlyphChip({ size, children }: { size: number; children: ReactNode }) {
- return {children};
+ return {children};
}
-const cardHeaderStyle = {
- display: 'flex',
- alignItems: 'center',
- gap: 12,
- marginBottom: 12,
-} as const;
-
-const cardTitleStyle = {
- fontFamily: tokens.typography.h3.family,
- fontSize: 18,
- lineHeight: 1.3,
- fontWeight: 600,
- color: tokens.colors.textPrimary,
- margin: 0,
-} as const;
-
-const attributionStyle = {
- fontFamily: tokens.typography.fontMono,
- fontSize: 10,
- lineHeight: 1.4,
- textTransform: 'uppercase',
- letterSpacing: '0.08em',
- color: tokens.colors.textMuted,
- marginTop: 2,
-} as const;
-
-const cardBlurbStyle = {
- fontFamily: tokens.typography.body.family,
- fontSize: tokens.typography.body.size,
- lineHeight: tokens.typography.body.line,
- color: tokens.colors.textSecondary,
- margin: 0,
-} as const;
-
-const ctaStyle = {
- fontFamily: tokens.typography.fontSans,
- fontSize: 14,
- fontWeight: 600,
- color: tokens.colors.accent,
-} as const;
-
-const snippetRowStyle = {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- gap: 8,
- background: tokens.surfaces.surface,
- border: `1px solid ${tokens.surfaces.border}`,
- borderRadius: tokens.radius.md,
- padding: '5px 6px 5px 12px',
- margin: '14px 0 16px',
-} as const;
-
-const snippetCodeStyle = {
- fontFamily: tokens.typography.fontMono,
- fontSize: 13,
- color: tokens.colors.textSecondary,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
-} as const;
-
-const gridStyle = {
- display: 'grid',
- gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
- gap: 16,
-} as const;
-
-const helperStyle = {
- fontFamily: tokens.typography.body.family,
- fontSize: 14,
- color: tokens.colors.textSecondary,
- margin: 0,
- marginTop: 16,
- textAlign: 'center',
-} as const;
-
-const helperLinkStyle = {
- color: tokens.colors.accent,
- fontWeight: 600,
-} as const;
-
-const fillHeightStyle = {
- height: '100%',
-} as const;
-
-const dividerStyle = {
- height: 1,
- background: tokens.surfaces.border,
- border: 'none',
- margin: '0 0 40px',
-} as const;
-
export default function DocsLandingPage() {
return (
<>
@@ -318,35 +172,14 @@ export default function DocsLandingPage() {
{/* Hero */}
-
-
+
+
Documentation
-
+
Start building with Threadplane
-
+
Streaming agent interfaces with runtime adapters, a shared Agent contract,
and a drop-in chat surface. Most packages are MIT; @threadplane/chat is
dual-licensed for noncommercial/evaluation and commercial production use.
@@ -359,34 +192,34 @@ export default function DocsLandingPage() {
Pick your backend
-
+
{BACKENDS.map((b) => (
-
-
-
+
+
+
- {b.title}
- {b.attribution}
+ {b.title}
+ {b.attribution}
- {b.blurb}
-
- {b.install}
+ {b.blurb}
+
+ {b.install}
- Adapter quickstart →
+ Adapter quickstart →
))}
-
+
Not sure which to use?{' '}
-
+
Choosing an adapter →
{' '}Want the drop-in UI first?{' '}
-
+
Chat quickstart →
@@ -396,28 +229,28 @@ export default function DocsLandingPage() {
{/* Step 2 — generative UI */}
-
+
Generative UI
-
+
{GENERATIVE_UI.map((g) => (
-
-
-
+
+
+
- {g.title}
- {g.attribution}
+ {g.title}
+ {g.attribution}
- {g.blurb}
- Get started →
+ {g.blurb}
+ Get started →
))}
-
+
Which fits my use case?{' '}
-
+
json-render vs A2UI →
@@ -427,18 +260,18 @@ export default function DocsLandingPage() {
{/* Step 3 — chat */}
-
+
Chat UI
-
-
-
+
+
+
- Chat
- Threadplane
+ Chat
+ Threadplane
-
+
Drop-in chat components — message list, input, streaming, tool
calls, interrupts, subagents. Renders A2UI & json-render surfaces
inline.
@@ -451,19 +284,19 @@ export default function DocsLandingPage() {
{/* Supporting libraries */}
-
+
Supporting libraries
-
+
{SUPPORTING.map((s) => {
const Glyph = GLYPHS[s.glyph];
return (
-
-
-
+
+
+
- {s.title}
- {s.blurb}
+ {s.title}
+ {s.blurb}
@@ -477,33 +310,11 @@ export default function DocsLandingPage() {
{/* Search prompt */}
-
-
+
+
Looking for something specific?
-
+
Press ⌘K to search the docs.
diff --git a/apps/website/src/styles/pages.css b/apps/website/src/styles/pages.css
index 55730ebf4..df170a587 100644
--- a/apps/website/src/styles/pages.css
+++ b/apps/website/src/styles/pages.css
@@ -8,3 +8,364 @@
*
* Migration: docs/superpowers/plans/2026-08-29-inline-style-substrate-migration.md
*/
+
+/* Licensing page — app/docs/licensing/page.tsx */
+.licensing-hero-inner {
+ max-width: 720px;
+}
+.licensing-eyebrow-spaced {
+ margin-bottom: 16px;
+}
+.licensing-h1 {
+ font-family: var(--font-garamond);
+ font-size: var(--text-h1);
+ line-height: var(--text-h1--line-height);
+ font-weight: 700;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 16px;
+ letter-spacing: -0.02em;
+}
+.licensing-subtitle {
+ font-family: var(--font-inter);
+ font-size: var(--text-body-lg);
+ line-height: var(--text-body-lg--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+ max-width: 60ch;
+}
+.licensing-section-inner {
+ max-width: 760px;
+}
+.licensing-section-inner-wide {
+ max-width: 900px;
+}
+.licensing-h2 {
+ font-family: var(--font-garamond);
+ font-size: var(--text-h2);
+ line-height: var(--text-h2--line-height);
+ font-weight: 700;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 16px;
+ letter-spacing: -0.01em;
+}
+.licensing-h2-spaced {
+ margin-top: 32px;
+}
+.licensing-h3 {
+ font-family: var(--font-inter);
+ font-size: var(--text-h3);
+ line-height: var(--text-h3--line-height);
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-top: 24px;
+ margin-bottom: 8px;
+}
+.licensing-body {
+ font-family: var(--font-inter);
+ font-size: var(--text-body);
+ line-height: var(--text-body--line-height);
+ color: var(--color-text-secondary);
+ margin: 0 0 16px;
+ max-width: 64ch;
+}
+.licensing-list {
+ padding-left: 24px;
+}
+.licensing-strong {
+ color: var(--color-text-primary);
+}
+.licensing-code-inline {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: "JetBrains Mono", monospace;
+}
+.licensing-code-block {
+ font-family: "JetBrains Mono", monospace;
+ font-size: 13px;
+ line-height: 1.6;
+ background: var(--color-surface-tinted);
+ border: 1px solid var(--color-border);
+ border-radius: 8px;
+ padding: 16px;
+ overflow: auto;
+ color: var(--color-text-primary);
+ margin: 0 0 16px;
+ white-space: pre;
+}
+.licensing-table-scroll {
+ overflow: auto;
+}
+.licensing-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-family: var(--font-inter);
+ font-size: 14px;
+ color: var(--color-text-secondary);
+ margin: 0 0 24px;
+}
+.licensing-cell {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--color-border);
+ vertical-align: top;
+}
+.licensing-header-cell {
+ color: var(--color-text-primary);
+ font-weight: 600;
+ background: var(--color-surface-tinted);
+}
+.licensing-footnote {
+ font-size: 13px;
+ color: var(--color-text-muted);
+}
+.licensing-cta-row {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-top: 16px;
+}
+.licensing-faq-link {
+ display: inline-flex;
+ align-items: center;
+ font-family: var(--font-inter);
+ font-size: 14px;
+ color: var(--color-accent);
+ text-decoration: none;
+ padding: 8px 12px;
+}
+
+/* Docs index page — app/docs/page.tsx */
+.docs-index-hero-inner {
+ max-width: 720px;
+}
+.docs-index-eyebrow-spaced {
+ margin-bottom: 16px;
+}
+.docs-index-h1 {
+ font-family: var(--font-garamond);
+ font-size: var(--text-h1);
+ line-height: var(--text-h1--line-height);
+ font-weight: 700;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 16px;
+ letter-spacing: -0.02em;
+}
+.docs-index-subtitle {
+ font-family: var(--font-inter);
+ font-size: var(--text-body-lg);
+ line-height: var(--text-body-lg--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+ max-width: 52ch;
+}
+.docs-index-step-label {
+ font-family: var(--font-mono);
+ font-size: var(--text-eyebrow);
+ font-weight: var(--text-eyebrow--font-weight);
+ letter-spacing: var(--text-eyebrow--letter-spacing);
+ text-transform: uppercase;
+ line-height: var(--text-eyebrow--line-height);
+ color: var(--color-text-muted);
+ margin: 0;
+ margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.docs-index-step-badge {
+ width: 20px;
+ height: 20px;
+ border-radius: var(--radius-full);
+ background: var(--color-accent);
+ color: #fff;
+ font-size: 12px;
+ font-weight: 700;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+}
+.docs-index-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
+}
+.docs-index-card-link {
+ text-decoration: none;
+}
+.docs-index-fill-height {
+ height: 100%;
+}
+.docs-index-card-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+/* Supporting-libraries card header omits the bottom margin in the source —
+ * a separate class rather than a shared one plus a zeroing modifier, since
+ * "cancel a shorthand's margin-bottom back to nothing" is not otherwise
+ * needed anywhere else in this file. */
+.docs-index-card-header-inline {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+.docs-index-logo-chip {
+ width: 30px;
+ height: 30px;
+ border-radius: var(--radius-md);
+ background: var(--color-surface);
+ border: 1px solid var(--color-border);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+}
+.docs-index-logo-img {
+ width: 18px;
+ height: 18px;
+ object-fit: contain;
+}
+/* GlyphChip's width/height come from a `size: number` prop (unbounded) and
+ * stay inline; everything else in glyphChipStyle moves here. */
+.docs-index-glyph-chip {
+ border-radius: var(--radius-md);
+ background: var(--color-accent-surface);
+ border: 1px solid var(--color-accent-border);
+ color: var(--color-accent);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+}
+.docs-index-card-title {
+ font-family: var(--font-inter);
+ font-size: 18px;
+ line-height: 1.3;
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+}
+.docs-index-card-title-sm {
+ font-size: 16px;
+}
+.docs-index-attribution {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: "JetBrains Mono", monospace;
+ font-size: 10px;
+ line-height: 1.4;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--color-text-muted);
+ margin-top: 2px;
+}
+.docs-index-card-blurb {
+ font-family: var(--font-inter);
+ font-size: var(--text-body);
+ line-height: var(--text-body--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+}
+.docs-index-card-blurb-sm {
+ font-size: 13px;
+ margin-top: 2px;
+}
+.docs-index-cta {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: Inter, system-ui, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--color-accent);
+}
+.docs-index-cta-block {
+ display: inline-block;
+ margin-top: 14px;
+}
+.docs-index-snippet-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ background: var(--color-surface);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ padding: 5px 6px 5px 12px;
+ margin: 14px 0 16px;
+}
+.docs-index-snippet-code {
+ /* raw font stack, not the `.family` var — see the font-exception note atop ui.css */
+ font-family: "JetBrains Mono", monospace;
+ font-size: 13px;
+ color: var(--color-text-secondary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.docs-index-helper {
+ font-family: var(--font-inter);
+ font-size: 14px;
+ color: var(--color-text-secondary);
+ margin: 0;
+ margin-top: 16px;
+ text-align: center;
+}
+.docs-index-helper-link {
+ color: var(--color-accent);
+ font-weight: 600;
+}
+.docs-index-divider {
+ height: 1px;
+ background: var(--color-border);
+ border: none;
+ margin: 0 0 40px;
+}
+.docs-index-search-inner {
+ text-align: center;
+ max-width: 640px;
+ margin: 0 auto;
+}
+.docs-index-search-heading {
+ font-family: var(--font-inter);
+ font-size: 22px;
+ line-height: 1.3;
+ font-weight: 600;
+ color: var(--color-text-primary);
+ margin: 0;
+ margin-bottom: 12px;
+}
+.docs-index-search-copy {
+ font-family: var(--font-inter);
+ font-size: var(--text-body);
+ line-height: var(--text-body--line-height);
+ color: var(--color-text-secondary);
+ margin: 0;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* Choosing-an-adapter page — app/docs/choosing-an-adapter/page.tsx */
+.adapter-hero-inner {
+ max-width: 720px;
+}
+.adapter-eyebrow-spaced {
+ margin-bottom: 16px;
+}
+.adapter-article {
+ max-width: 760px;
+}
+
+/* Docs shell — app/docs/[library]/[section]/[slug]/page.tsx
+ * paddingTop: 80 and the overflow-x-hidden utility are known defects
+ * (nav-height coupling) migrated VERBATIM here; a later project fixes them,
+ * not this migration. */
+.docs-shell-page {
+ background: var(--color-canvas);
+ padding-top: 80px;
+}
+.docs-shell-body {
+ background: var(--color-surface);
+}