Skip to content

Canonical privacy policy: one page, retired docs library, public copy gate - #980

Merged
blove merged 8 commits into
mainfrom
blove/canonical-privacy-policy
Sep 3, 2026
Merged

Canonical privacy policy: one page, retired docs library, public copy gate#980
blove merged 8 commits into
mainfrom
blove/canonical-privacy-policy

Conversation

@blove

@blove blove commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Replaces Threadplane's scattered analytics promises with a single /privacy policy, retires the dedicated telemetry docs library behind permanent redirects, and adds a gate that checks served production output rather than source.

Implements docs/superpowers/plans/2026-08-31-threadplane-canonical-privacy-policy.md, the first of the three plans the approved growth design decomposed into.

What changes

One canonical policy. /privacy states the collected categories, names every processor (Vercel, Neon, PostHog, Resend, Google, Anthropic), declares indefinite default retention, and explains deletion, opt-out, and contact. It describes categories and purposes rather than an event catalog: a published catalog goes stale the first time an event changes, and a stale one is worse than a general one. Linked from the footer on every page and listed in the sitemap.

The promise surfaces are gone. The "What we won't do" section is deleted outright rather than trimmed, along with both call sites, the FinalCTA caption, and the YesWall install claim (16 → 15 questions, count still derived from the data). Removing the caption prop also removed two descriptive captions on the home and AG-UI pages — the surface goes, not just the promises on it.

The telemetry docs library is retired. Seven files deleted and the library removed from docsConfig, the LibraryId union, the docs home card, and the glyph map, so navigation, search, static params, sitemap, and markdown lookup all lose it through shared configuration. All four retired route families answer 308 to /privacy — the public docs root and descendants, and the markdown API root and descendants.

Claims fixed at their source, not downstream. Four TSDoc comments asserted "No telemetry is emitted unless this is provided" and were rendered into published API docs. The same comment ships in the .d.ts and every IDE tooltip, which no website-side filter can reach, so generation now fails on the raw TSDoc and names the offending sentence rather than quietly cleaning the website. A pure projection remains as a second line for anything phrased differently.

A gate on served output. e2e/public-copy.spec.ts crawls every sitemap route plus an explicit registry of non-indexed public routes, and runs against a real production build in CI. One shared contract module feeds both the source scan and the crawl so they cannot drift.

Scope decision worth review

The plan asked that no public surface contain the word "telemetry", while also placing AgentRuntimeTelemetrySink out of scope. Those cannot both hold: telemetry is the literal name of a documented config field on provideAgent(), toAgent(), and both AgentConfig types.

The gate therefore bars the claims and the narrative positioning, and a companion test asserts the API rows still exist — so a future cleanup cannot satisfy the bans by deleting documentation for a shipped option.

Two bugs this surfaced

  • next.config.spec.ts had never run. It sits at the app root, which none of the vitest include patterns matched, so its PostHog rewrite assertions had been dead since they were written. Root specs are now included; the existing assertions pass.
  • /blog served zero posts in the production build. nx serve --configuration=production runs with dist as its cwd and dist carries no content/, so routes reading MDX at request time returned an empty list. The gate would have passed against a page no visitor sees. Production mode now links the content in before serving.

Testing

  • Website unit suite and lint green
  • 128 E2E against a real production build, including the 11 new gate tests
  • Production build green; /privacy prerenders as static
  • Guards verified non-vacuous by mutation: injecting a claim into /about fails the crawl and names the route; injecting one into committed API JSON fails the scan; injecting one into TSDoc aborts generation

🤖 Generated with Claude Code

blove and others added 7 commits September 2, 2026 20:37
One /privacy route replaces the scattering of analytics promises: what is
collected, why, which processors receive it, indefinite default retention, and
how to unsubscribe or request deletion.

It describes categories and purposes rather than an event catalog. A published
catalog goes stale the first time an event changes, and a stale one is worse
than a general one; the page also avoids installation claims and absolute
guarantees it cannot continuously verify. Its spec fails if any of those grow
back.

Also lists /privacy in the sitemap inventory and links it from the footer
bottom bar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes the What we won't do section outright rather than trimming it to a
smaller set of claims, and removes both its call sites. Also drops the
FinalCTA trailing caption, the YesWall install/no-phone-home row, and the
orphaned CSS for all three.

The specs now fail if any of them return: FinalCTA renders no caption element,
YesWall carries fifteen questions with no phone-home claim, and both counts
stay derived from the data so the label cannot drift.

Reworded Pilot-to-Prod's OpenTelemetry hooks to Distributed tracing hooks.

Removing the caption prop also removes two descriptive call-site captions on
the home and AG-UI pages. That is the approved scope: the surface goes, not
just the promises on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… redirects

Deletes the seven dedicated files and removes the library from docsConfig, the
LibraryId union, the docs home card, and the LibraryMark glyph map; navigation,
search, static params, sitemap, and markdown lookup all lose it through the
shared configuration.

Delivered links and indexed results outlive a deletion, so all four retired
route families — the public docs root and descendants, and the markdown API
root and descendants — permanently redirect to /privacy. Browser tests walk
each one.

Two ag-ui docs tables kept documenting the sink option but lost their link to
the retired library, and llms-full.txt no longer imports the deleted API JSON.

Also revives next.config.spec.ts: it sits at the app root, which none of the
vitest include patterns matched, so its PostHog rewrite assertions had never
run. Root specs are now included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a public-copy scan that fails on the barred absolute claims — phone home,
installation is inert, off by default, what we won't do, no telemetry is
emitted, we never collect — and on narrative positioning around the word.

Rewrites the three blog posts and the ag-ui, chat, langgraph, and render docs
in observability and instrumentation language, and deletes the three lifecycle
guides' Privacy sections outright rather than softening them: they asserted
absolutes no test keeps true, and /privacy now covers the subject once.

Public /api/ingest bodies become Invalid event payload, Event ingest is not
configured, and Event ingest failed. The internal log prefix and type names
are untouched, and a test pins that split in both directions.

The scan covers hand-authored MDX only. Generated API JSON still carries the
old guarantee text; the projection that cleans it is the next task, and the
scan grows to cover it there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Generated API docs are TSDoc rendered as published copy, so a claim written in
a doc comment became site content without anyone reviewing it as content. Four
comments asserted 'No telemetry is emitted unless this is provided' and shipped
straight through.

Fixes those at the source rather than only downstream: the same comment is the
.d.ts and the IDE tooltip, which no website-side filter can reach. Generation
now fails on the raw entries and names the offending sentence, so the author is
sent back to the comment instead of the site being quietly cleaned.

Adds a pure projection as the second line: it strips a barred claim from any
description, keeps every name, type, and structure verbatim, and does not
mutate its input. Renaming AgentRuntimeTelemetry* or a config field would make
the docs disagree with the shipped types, which is the worse failure.

The narrative generator now reads a six-library allowlist instead of walking
the directory, so a retired library reappearing on disk cannot feed it again,
and the public-copy scan covers generated JSON as well as authored MDX.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The source scan reads the repository; this reads what a visitor receives. They
can disagree — a template, a generated bundle, or a response body can carry a
claim no .mdx file contains — and the served side is the one that ships.

Crawls every sitemap route plus an explicit registry of non-indexed public
routes (AGENTS.md, CLAUDE.md, both LLM endpoints, representative markdown API
output), asserts the retired routes still answer 308 to /privacy, and checks
public /api/ingest error bodies. A future public route such as /connect belongs
in that registry in the change that creates it, or it ships unchecked.

Both the scan and the crawl now import one contract module, so the two cannot
drift apart — and drift would land on the deployed side, which is harder to
notice.

Two things this surfaced:

-
 NX   It's time to update Nx 🎉

Your repository uses a higher version of Nx (22.5.1) than your global CLI version (21.5.2)
For more information, see https://nx.dev/more-concepts/global-nx

 NX   Cannot find configuration for task root:serve

Pass --verbose to see the stacktrace. runs with dist as its cwd, and dist
  carries no content/, so routes reading MDX at request time served an empty
  list. /blog rendered zero posts. The gate would have passed against a page no
  visitor sees, so production mode links the content in before serving.
- CI ran the website E2E only in dev mode. The gate now runs a second time
  against a real production build.

Verified non-vacuous: injecting a claim into /about fails the crawl and names
the route.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 3, 2026 6:17am UTC

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

The gate crawls every sitemap route. Against a prebuilt production server that
is about two seconds; against `next dev` each route compiles on demand, and
the ordinary CI e2e job timed out on it.

I had only ever run the gate in production mode locally, so the dev-mode cost
never showed up until CI. Both modes are now exercised before pushing.

Scoped to production mode, which is also where the plan puts it: the answers
that matter come from the built output, not from a dev bundle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 440bcac into main Sep 3, 2026
75 checks passed
blove added a commit that referenced this pull request Sep 4, 2026
…the Yes Wall

main's #996 replaced the Yes wall with the reliability band: ProofStrip moves
into the dark contrast slot under the logo ribbon and the Yes wall is deleted.
That supersedes this branch's Task 12, so the Yes wall work is dropped rather
than merged.

- page.tsx: ProofStrip (dark) sits directly under LogoRibbon, ahead of
  RuntimeParity/ThreeSteps. YesWall is gone; Promises went with #980 and its
  component no longer exists on main. StackDiagramSection and HomeConceptGrid
  stay unmounted here (they still ship on /langgraph and /ag-ui). Metadata
  keeps HOME_TITLE/HOME_DESCRIPTION.
- landing.css: main's proof-strip block wholesale, including its min-width:0
  fix, which supersedes this branch's repeat(2, minmax(0, 1fr)) from 5b6328b.
  The .yes-wall-* rules and the Task 12 expander are deleted.
- style-contracts.spec.ts: the .yes-wall-row[hidden] contract is removed; the
  rule it guarded no longer exists.
- FinalCTA.tsx: keeps this branch's optional ctaId on primary/secondary and
  captionLink, and honours main's retirement of the promise caption — the
  caption prop stays, but its default is null instead of the old
  "Installation is inert" line, which main's e2e now asserts is absent.
- analytics/events.ts + taxonomy.md: home_production_readiness_expand joins
  home_yes_wall_docs as retired; neither has a firing site any more.
- e2e: the Yes Wall expander test is dropped from home-hero.spec.ts;
  website.spec.ts keeps main's dark-proof-band assertion alongside this
  branch's five-FeatureBlock and H1 assertions.
- HomeFAQ: the branch's telemetry question made a barred absolute claim
  ("Installation is inert") and linked the retired /docs/telemetry library,
  both of which main's public-copy contract now forbids. Replaced with a
  reporting question answered from /privacy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove added a commit that referenced this pull request Sep 4, 2026
…ialog

Rebuilds the top of threadplane.ai and reorders the bottom, per
docs/superpowers/specs/2026-09-02-homepage-rebuild-design.md. The demo-side
/hero route this embeds shipped in #976.

Rewritten: stacked category hero, InstallDialog (fake/LangGraph/AG-UI),
HeroDemo (server-rendered poster as the LCP, origin-checked postMessage
bridge to demo.threadplane.ai/hero, poster fallback), LogoRibbon into three
labelled compatibility rows, HomeFAQ, PilotBlock, FinalCTA.

New: RuntimeParity, ThreeSteps, CodingAgentQuickstart, ScopeTable, a shared
Modal primitive, and a Try-without-a-backend docs page verified from a clean
ng new application.

All homepage copy, install commands and snippets are single-sourced in
src/lib/positioning.ts behind drift guards that resolve package names and
peers against libs/*/package.json, parse every snippet through the TypeScript
compiler, and pin the license word and Angular range to their manifests.

Adopts main's decisions where they superseded the spec: the Yes wall stays
deleted and the reliability band keeps its slot and styling (#996), Promises
stays unmounted, and the telemetry FAQ answer uses the privacy-policy
language the public-copy contract requires (#980).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove added a commit that referenced this pull request Sep 4, 2026
…ialog (#997)

Rebuilds the top of threadplane.ai and reorders the bottom, per
docs/superpowers/specs/2026-09-02-homepage-rebuild-design.md. The demo-side
/hero route this embeds shipped in #976.

Rewritten: stacked category hero, InstallDialog (fake/LangGraph/AG-UI),
HeroDemo (server-rendered poster as the LCP, origin-checked postMessage
bridge to demo.threadplane.ai/hero, poster fallback), LogoRibbon into three
labelled compatibility rows, HomeFAQ, PilotBlock, FinalCTA.

New: RuntimeParity, ThreeSteps, CodingAgentQuickstart, ScopeTable, a shared
Modal primitive, and a Try-without-a-backend docs page verified from a clean
ng new application.

All homepage copy, install commands and snippets are single-sourced in
src/lib/positioning.ts behind drift guards that resolve package names and
peers against libs/*/package.json, parse every snippet through the TypeScript
compiler, and pin the license word and Angular range to their manifests.

Adopts main's decisions where they superseded the spec: the Yes wall stays
deleted and the reliability band keeps its slot and styling (#996), Promises
stays unmounted, and the telemetry FAQ answer uses the privacy-policy
language the public-copy contract requires (#980).

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant