From 285a1ce1a65d557cc740a939d50e0d05da8e998d Mon Sep 17 00:00:00 2001 From: "helpcode.ai" <340500827+helpcodeai@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:45:54 +0200 Subject: [PATCH] Remove the GitHub star count from emails, sign-in and sign-up pages The trust band in the emails now shows downloads, AI tool calls last month and Cloud workspaces as tiles of one row. The OAuth consent and sign-in pages and the sign-up panel say "Open source on GitHub" without a number. /api/public/stats still returns githubStars; nothing displays it. Matches the website (anythingmcp-website #350). --- packages/backend/src/auth/auth-page.ts | 8 ++---- .../backend/src/auth/login.controller.spec.ts | 15 +++++------ packages/backend/src/auth/login.controller.ts | 13 ++-------- .../src/public-stats/trust-stats.format.ts | 26 +++++++------------ .../src/public-stats/trust-stats.spec.ts | 19 +++----------- packages/backend/src/settings/email-layout.ts | 11 +------- .../src/settings/email-templates.spec.ts | 5 ++-- .../src/settings/email.service.spec.ts | 6 ++--- .../src/settings/unsubscribe.controller.ts | 2 +- packages/frontend/src/app/login/page.tsx | 2 +- .../frontend/src/components/auth-trust.tsx | 26 +++++-------------- packages/frontend/src/lib/trust-stats.ts | 17 ++++-------- .../tests/e2e/signup-trust-panel.spec.ts | 4 +-- 13 files changed, 46 insertions(+), 108 deletions(-) diff --git a/packages/backend/src/auth/auth-page.ts b/packages/backend/src/auth/auth-page.ts index 05ecb22d2..970e6693d 100644 --- a/packages/backend/src/auth/auth-page.ts +++ b/packages/backend/src/auth/auth-page.ts @@ -98,8 +98,6 @@ export function clientTilePair(clientName: string, redirectHost: string): string export interface TrustRowOptions { /** Cloud-only claims (Frankfurt, DPA) are shown only on AnythingMCP Cloud. */ cloud: boolean; - /** GitHub stars, formatted ("984", "1,100+"), or null when unknown. */ - stars: string | null; } export function trustRow(opts: TrustRowOptions): string { @@ -107,9 +105,7 @@ export function trustRow(opts: TrustRowOptions): string { `${LOCK_SVG} AES-256-GCM encryption`, ...(opts.cloud ? ['🇪🇺 Hosted in Frankfurt', 'GDPR · DPA'] : []), 'Every call in your audit log', - opts.stars - ? `★ ${escapeHtml(opts.stars)} on GitHub · open source` - : 'Open source on GitHub', + 'Open source on GitHub', ]; return `
${items.join('')}
AnythingMCP by helpcode.ai GmbH, Freiburg, Germany
`; @@ -189,7 +185,7 @@ const STYLES = ` .trust { max-width: 440px; width: 100%; margin-top: 18px; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 16px; font-size: 12px; color: var(--text-3); } .trust span { display: inline-flex; align-items: center; gap: 6px; } - .trust .gh { color: var(--text-2); font-weight: 500; } .trust .star { color: #e3a008; } + .trust .gh { color: var(--text-2); font-weight: 500; } .foot { margin-top: 10px; font-size: 11.5px; color: var(--text-3); text-align: center; } @media (max-width: 480px) { body { padding: 16px 12px 24px; justify-content: flex-start; } diff --git a/packages/backend/src/auth/login.controller.spec.ts b/packages/backend/src/auth/login.controller.spec.ts index 36fcca71a..5ab031705 100644 --- a/packages/backend/src/auth/login.controller.spec.ts +++ b/packages/backend/src/auth/login.controller.spec.ts @@ -746,7 +746,7 @@ describe('LoginController', () => { cloud: { mode: 'cloud', isCloud: () => true, isSelfHosted: () => false }, selfHosted: { mode: 'self-hosted', isCloud: () => false, isSelfHosted: () => true }, }; - const make = (deployment: object, p: object = prisma, trustStats?: object) => + const make = (deployment: object, p: object = prisma) => new LoginController( authService as unknown as AuthService, p as unknown as PrismaService, @@ -755,9 +755,6 @@ describe('LoginController', () => { sso as unknown as SsoService, deployment as any, grants as any, - undefined, - undefined, - trustStats as any, ); const session = (redirectUri: string, clientName: string) => { store.getOAuthSession.mockResolvedValue({ @@ -846,14 +843,14 @@ describe('LoginController', () => { expect(html.indexOf('value="approve"')).toBeLessThan(html.indexOf('value="deny"')); }); - it('makes Frankfurt and DPA claims on Cloud only, with the live star count', async () => { + it('makes Frankfurt and DPA claims on Cloud only, and shows no star count', async () => { session('https://claude.ai/api/mcp/auth_callback', 'Claude'); - const stats = { peek: () => ({ githubStars: 1234 }) }; - const cloudHtml = await page(make(DEPLOY.cloud, prisma, stats)); + const cloudHtml = await page(make(DEPLOY.cloud, prisma)); expect(cloudHtml).toContain('Hosted in Frankfurt'); expect(cloudHtml).toContain('GDPR · DPA'); - expect(cloudHtml).toContain('1,200+ on GitHub'); - const selfHtml = await page(make(DEPLOY.selfHosted, prisma, stats)); + expect(cloudHtml).toContain('Open source on GitHub'); + expect(cloudHtml).not.toContain('★'); + const selfHtml = await page(make(DEPLOY.selfHosted, prisma)); expect(selfHtml).not.toMatch(/Frankfurt|DPA/); expect(selfHtml).toContain('AES-256-GCM'); }); diff --git a/packages/backend/src/auth/login.controller.ts b/packages/backend/src/auth/login.controller.ts index 4e77476a6..70fcf64f8 100644 --- a/packages/backend/src/auth/login.controller.ts +++ b/packages/backend/src/auth/login.controller.ts @@ -28,8 +28,6 @@ import { providerMarkSvg } from './provider-marks'; import { McpConnectionGrantService } from '../mcp-servers/mcp-connection-grant.service'; import { ProductEvents, ProductEventService } from '../audit/product-event.service'; import { LicenseGuardService } from '../license/license-guard.service'; -import { TrustStatsService } from '../public-stats/trust-stats.service'; -import { formatStars } from '../public-stats/trust-stats.format'; import { AMCP_MARK_SVG as AMCP_MARK, claudeMark, @@ -96,18 +94,11 @@ export class LoginController { private readonly grants: McpConnectionGrantService, @Optional() private readonly productEvents?: ProductEventService, @Optional() private readonly licenseGuard?: LicenseGuardService, - @Optional() private readonly trustStats?: TrustStatsService, ) {} - /** The trust row under every page of the flow. Never waits on GitHub. */ + /** The trust row under every page of the flow. */ private trust(): TrustRowOptions { - let stars: string | null = null; - try { - stars = formatStars(this.trustStats?.peek().githubStars); - } catch { - stars = null; - } - return { cloud: this.deployment.isCloud(), stars }; + return { cloud: this.deployment.isCloud() }; } /** diff --git a/packages/backend/src/public-stats/trust-stats.format.ts b/packages/backend/src/public-stats/trust-stats.format.ts index f026bf2ff..8d5bbf92c 100644 --- a/packages/backend/src/public-stats/trust-stats.format.ts +++ b/packages/backend/src/public-stats/trust-stats.format.ts @@ -1,9 +1,13 @@ /** * The public trust numbers (GitHub stars, Docker pulls, Cloud workspaces, AI - * tool calls in the last 30 days) and how they are shown. Stars, downloads and - * tool calls are never shown below TRUST_FLOORS; above a floor the live number - * is shown, rounded DOWN. Workspaces are live only: a value below its rounding + * tool calls in the last 30 days) and how they are shown. Downloads and tool + * calls are never shown below TRUST_FLOORS; above a floor the live number is + * shown, rounded DOWN. Workspaces are live only: a value below its rounding * unit is not shown at all rather than as "0+". + * + * GitHub stars are still collected and served by /api/public/stats, but shown + * nowhere: Matteo took them off the website, the emails and the sign-in pages + * on 10 Oct 2026. */ export interface TrustStats { githubStars: number | null; @@ -15,8 +19,6 @@ export interface TrustStats { } export interface TrustStatsDisplay { - /** "984" below 1,000, then "1,000+", "1,100+". */ - stars: string | null; /** "32,000+". */ downloads: string | null; /** "3,800+". */ @@ -26,12 +28,11 @@ export interface TrustStatsDisplay { } /** - * The least each figure is shown as (Matteo, 8 Oct 2026): 1,000+ stars, - * 200,000+ downloads across all distribution channels, 1M+ AI tool calls last - * month. Docker Hub alone undercounts downloads, so the floor applies even + * The least each figure is shown as (Matteo, 8 Oct 2026): 200,000+ downloads + * across all distribution channels, 1M+ AI tool calls last month. Docker Hub alone undercounts downloads, so the floor applies even * when a live number is missing or lower. */ -export const TRUST_FLOORS = { githubStars: 1_000, dockerPulls: 200_000, toolCalls30d: 1_000_000 } as const; +export const TRUST_FLOORS = { dockerPulls: 200_000, toolCalls30d: 1_000_000 } as const; const atLeast = (n: number | null | undefined, floor: number) => (usable(n) && n > floor ? n : floor); @@ -54,12 +55,6 @@ function floorPlus(n: number | null | undefined, unit: number): string | null { return `${thousands(Math.floor(n / unit) * unit)}+`; } -export function formatStars(n: number | null | undefined): string | null { - if (!usable(n)) return null; - const whole = Math.floor(n); - return whole < 1000 ? thousands(whole) : floorPlus(whole, 100); -} - export function formatDownloads(n: number | null | undefined): string | null { return floorPlus(n, 1000); } @@ -76,7 +71,6 @@ export function formatToolCalls(n: number | null | undefined): string | null { export function formatTrustStats(stats: TrustStats | null | undefined): TrustStatsDisplay { return { - stars: formatStars(atLeast(stats?.githubStars, TRUST_FLOORS.githubStars)), downloads: formatDownloads(atLeast(stats?.dockerPulls, TRUST_FLOORS.dockerPulls)), workspaces: formatWorkspaces(stats?.workspaces), toolCalls: formatToolCalls(atLeast(stats?.toolCalls30d, TRUST_FLOORS.toolCalls30d)), diff --git a/packages/backend/src/public-stats/trust-stats.spec.ts b/packages/backend/src/public-stats/trust-stats.spec.ts index 7f26fd4f5..e60df72c2 100644 --- a/packages/backend/src/public-stats/trust-stats.spec.ts +++ b/packages/backend/src/public-stats/trust-stats.spec.ts @@ -1,7 +1,6 @@ import axios from 'axios'; import { formatDownloads, - formatStars, formatToolCalls, formatTrustStats, formatWorkspaces, @@ -13,18 +12,6 @@ jest.mock('axios'); const mockedGet = axios.get as jest.Mock; describe('trust number formatting (always rounded down)', () => { - it.each([ - [984, '984'], - [999, '999'], - [1000, '1,000+'], - [1099, '1,000+'], - [1100, '1,100+'], - [12_345, '12,300+'], - [0, null], - [null, null], - [Number.NaN, null], - ])('stars %p -> %p', (n, out) => expect(formatStars(n as any)).toBe(out)); - it.each([ [32_456, '32,000+'], [999, null], @@ -48,11 +35,11 @@ describe('trust number formatting (always rounded down)', () => { it('formats a whole set, never below the floors', () => { expect( formatTrustStats({ githubStars: 984, dockerPulls: 32_456, workspaces: 3_871, toolCalls30d: 654_321, updatedAt: null }), - ).toEqual({ stars: '1,000+', downloads: '200,000+', workspaces: '3,800+', toolCalls: '1M+' }); + ).toEqual({ downloads: '200,000+', workspaces: '3,800+', toolCalls: '1M+' }); expect( formatTrustStats({ githubStars: 1_234, dockerPulls: 250_400, workspaces: null, toolCalls30d: 2_100_000, updatedAt: null }), - ).toEqual({ stars: '1,200+', downloads: '250,000+', workspaces: null, toolCalls: '2M+' }); - expect(formatTrustStats(null)).toEqual({ stars: '1,000+', downloads: '200,000+', workspaces: null, toolCalls: '1M+' }); + ).toEqual({ downloads: '250,000+', workspaces: null, toolCalls: '2M+' }); + expect(formatTrustStats(null)).toEqual({ downloads: '200,000+', workspaces: null, toolCalls: '1M+' }); }); }); diff --git a/packages/backend/src/settings/email-layout.ts b/packages/backend/src/settings/email-layout.ts index 8d16a663a..1258112a3 100644 --- a/packages/backend/src/settings/email-layout.ts +++ b/packages/backend/src/settings/email-layout.ts @@ -19,7 +19,6 @@ export const EMAIL_COLORS = { muted: '#6b665c', blue: '#2159e0', tint: '#eef2fd', - star: '#e3a008', } as const; const C = EMAIL_COLORS; @@ -193,13 +192,11 @@ const CLIENTS: Array<{ file: string; alt: string; label: string }> = [ function trustBand(ctx: EmailBrandContext, assetBase: string): string { const { stats, cloud } = ctx; const tiles: Array<{ big: string; small: string }> = []; - if (stats.stars) { - tiles.push({ big: `★ ${esc(stats.stars)}`, small: 'stars on GitHub' }); - } if (stats.downloads) tiles.push({ big: esc(stats.downloads), small: 'downloads' }); // The tool-call count is AnythingMCP Cloud's. A self-hosted instance only // knows its own, which would read as if it were everyone's. if (cloud && stats.toolCalls) tiles.push({ big: esc(stats.toolCalls), small: 'AI tool calls last month' }); + if (cloud && stats.workspaces) tiles.push({ big: esc(stats.workspaces), small: 'workspaces on AnythingMCP Cloud' }); const width = tiles.length ? Math.floor(100 / tiles.length) : 100; const statRow = tiles.length ? ` @@ -212,10 +209,6 @@ ${tiles .join('\n')}
` : ''; - const workspaces = - cloud && stats.workspaces - ? `${esc(stats.workspaces)} workspaces on AnythingMCP Cloud` - : ''; const directory = cloud ? `${pill( ` Listed in the Claude Directory`, @@ -235,7 +228,6 @@ ${tiles ${statRow} -${workspaces} @@ -301,7 +293,6 @@ export function renderEmailText(input: { }): string { const { stats, cloud } = input.ctx; const numbers = [ - stats.stars ? `${stats.stars} stars on GitHub` : null, stats.downloads ? `${stats.downloads} downloads` : null, cloud && stats.toolCalls ? `${stats.toolCalls} AI tool calls last month` : null, cloud && stats.workspaces ? `${stats.workspaces} workspaces on AnythingMCP Cloud` : null, diff --git a/packages/backend/src/settings/email-templates.spec.ts b/packages/backend/src/settings/email-templates.spec.ts index 5e18aab18..bfae4e430 100644 --- a/packages/backend/src/settings/email-templates.spec.ts +++ b/packages/backend/src/settings/email-templates.spec.ts @@ -129,15 +129,16 @@ describe('trust band', () => { it('shows the numbers, never below their floors', () => { const html = verify.render(cloud, { name: 'A', other: 'B' }).html; - expect(html).toContain('1,000+'); expect(html).toContain('200,000+'); expect(html).toContain('1M+'); expect(html).toContain('3,800+'); + expect(html).toContain('workspaces on AnythingMCP Cloud'); + expect(html).not.toContain('stars on GitHub'); }); it('shows the floors without live numbers, and no workspace count', () => { const html = verify.render(noStats, { name: 'A', other: 'B' }).html; - expect(html).toContain('stars on GitHub'); + expect(html).not.toContain('stars on GitHub'); expect(html).toContain('200,000+'); expect(html).not.toContain('workspaces on'); expect(html).not.toMatch(/\b0\+/); diff --git a/packages/backend/src/settings/email.service.spec.ts b/packages/backend/src/settings/email.service.spec.ts index 1253edb28..81139c5b7 100644 --- a/packages/backend/src/settings/email.service.spec.ts +++ b/packages/backend/src/settings/email.service.spec.ts @@ -63,8 +63,8 @@ describe('EmailService', () => { expect(mail.text).toContain('123456'); expect(mail.headers).toBeUndefined(); expect(mail.html).not.toContain('Unsubscribe'); - // A live number above its floor, rounded down; the others at their floors. - expect(mail.html).toContain('1,200+'); + // Downloads and tool calls at their floors; no GitHub star count. + expect(mail.html).not.toContain('stars on GitHub'); expect(mail.html).toContain('200,000+'); expect(mail.html).toContain('1M+'); }); @@ -145,6 +145,6 @@ describe('EmailService', () => { trustStats.get.mockRejectedValue(new Error('down')); trustStats.peek.mockReturnValue({ githubStars: null, dockerPulls: null, workspaces: null, toolCalls30d: null, updatedAt: null }); await expect(service.sendExistingAccountEmail('a@b.com', 'https://cloud.anythingmcp.com/login', 'https://cloud.anythingmcp.com/forgot-password')).resolves.toBe(true); - expect(sent().html).toContain('1,000+'); + expect(sent().html).toContain('200,000+'); }); }); diff --git a/packages/backend/src/settings/unsubscribe.controller.ts b/packages/backend/src/settings/unsubscribe.controller.ts index 844959d73..30ca1f493 100644 --- a/packages/backend/src/settings/unsubscribe.controller.ts +++ b/packages/backend/src/settings/unsubscribe.controller.ts @@ -92,7 +92,7 @@ export class UnsubscribeController { renderAuthPage({ title: 'Email preferences — AnythingMCP', card, - trust: { cloud: this.deployment.isCloud(), stars: null }, + trust: { cloud: this.deployment.isCloud() }, }), ); } diff --git a/packages/frontend/src/app/login/page.tsx b/packages/frontend/src/app/login/page.tsx index f31a59e95..a8bc4d159 100644 --- a/packages/frontend/src/app/login/page.tsx +++ b/packages/frontend/src/app/login/page.tsx @@ -1126,7 +1126,7 @@ function LoginForm() { return (
{formCard} - {infoLoaded && } + {infoLoaded && }
); } diff --git a/packages/frontend/src/components/auth-trust.tsx b/packages/frontend/src/components/auth-trust.tsx index 064ca64de..f92e76831 100644 --- a/packages/frontend/src/components/auth-trust.tsx +++ b/packages/frontend/src/components/auth-trust.tsx @@ -35,11 +35,10 @@ export function TrustPanel({ directoryBadge?: boolean; }) { const tiles = [ - stats?.stars ? { n: stats.stars, l: 'stars on GitHub', star: true } : null, stats?.downloads ? { n: stats.downloads, l: 'downloads' } : null, stats?.toolCalls ? { n: stats.toolCalls, l: 'AI tool calls last month' } : null, stats?.workspaces ? { n: stats.workspaces, l: 'workspaces on AnythingMCP Cloud' } : null, - ].filter(Boolean) as Array<{ n: string; l: string; star?: boolean }>; + ].filter(Boolean) as Array<{ n: string; l: string }>; return (
Why teams build on AnythingMCP
${badges}