- apps/cli/ is now the canonical CLI (was apps/cli-v2/).
- apps/cli/ legacy v0 archived as branch 'legacy-cli-archive' and tag
'cli-v0-legacy-final' before deletion; git history preserves it too.
- .github/workflows/release-cli.yml paths updated.
- pnpm-lock.yaml regenerated.
Broker-side peer-grant enforcement (spec: 2026-04-15-per-peer-capabilities):
- 0020_peer-grants.sql adds peer_grants jsonb + GIN index on mesh.member.
- handleSend in broker fetches recipient grant maps once per send, drops
messages silently when sender lacks the required capability.
- POST /cli/mesh/:slug/grants to update from CLI; broker_messages_dropped_by_grant_total metric.
- CLI grant/revoke/block now mirror to broker via syncToBroker.
Auto-migrate on broker startup:
- apps/broker/src/migrate.ts runs drizzle migrate with pg_advisory_lock
before the HTTP server binds. Exits non-zero on failure so Coolify
healthcheck fails closed.
- Dockerfile copies packages/db/migrations into /app/migrations.
- postgres 3.4.5 added as direct broker dep.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- .github/workflows/release-cli.yml: build self-contained binaries via
`bun build --compile` for darwin/linux/windows × x64/arm64 on every
cli-v* tag, attach to GitHub Release with SHA256SUMS, auto-bump the
homebrew tap on non-prerelease versions.
- packaging/homebrew/claudemesh.rb.template: formula template for the
homebrew-claudemesh tap.
- packaging/winget/claudemesh.yaml.template: winget manifest template.
- /install script now detects absence of Node and downloads the
platform-appropriate binary from the GitHub Release, installs to
~/.claudemesh/bin, and shims into ~/.local/bin — zero Node required.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- /install shell script now points users at `claudemesh <invite-url>`
(one step) instead of the split join+launch
- InstallToggle first-time panel shows single copy-block with
install+launch on the same line
- Also advertises url-handler install and shell completions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- session_id (clm_sess_...) in browser URL — identifies login attempt
- user_code (ABCD-EFGH) visual confirmation — shown in both terminal and browser
- device_code (secret) — CLI polls with this, never displayed
- CLI accepts stdin paste of JWT token while polling (race)
- Web page handles both ?session= and ?code= params
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove dependency on SocialProviders/RegisterForm which need
React Query providers. Self-contained with authClient directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No more redirect to generic /auth/login. The /cli-auth?code=XXXX page
now shows auth forms inline (Google, GitHub, email) with device code
context — like Anthropic's "Build with Claude" page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Drizzle schema: device_code + cli_session tables in mesh pgSchema
- Broker endpoints: POST /cli/device-code, GET /cli/device-code/:code,
POST /cli/device-code/:code/approve, GET /cli/sessions
- Web app API routes now proxy to broker (no in-memory state)
- Tracks devices per user: hostname, platform, arch, last_seen, token_hash
- JWT signed with CLI_SYNC_SECRET, 30-day expiry
- Session revocation support via revokedAt column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New API endpoints:
- POST /api/auth/cli/device-code/new — issue device code + user code
- GET /api/auth/cli/device-code/[code] — poll device code status
- POST /api/auth/cli/device-code/[code]/approve — approve by device code
- POST /api/auth/cli/device-code/approve-by-user-code — approve by user code
Updated cli-auth page to auto-approve on page load after authentication
(no manual "Approve" button click needed).
Enables `claudemesh login` and `claudemesh register` CLI commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New hero section with a live animated mesh background: three equal
Claude Code peers in a triangle layout + six desaturated background
peers, all rendered pixel-perfect from pure React/CSS using the exact
Unicode characters and colors from Claude Code's own source.
- User prompts type into the bottom prompt-input box and "submit" to
scrollback (matching real Claude Code behavior). Mesh sends fly as
envelope icons with fading trails between peers; receivers pulse on
arrival. Dynamic routing by peer displayName.
- Radial vignette overlay keeps the hero title crisp while letting the
corner peers pulse visibly around the edges. Top/bottom linear fades
bleed into adjacent sections.
- Responsive scaling via ResizeObserver: cover-fit in hero bg context,
contain-fit for standalone use.
- Features section: added Skills, MCPs, and Commands as the first
three tabs — the mesh's real differentiators. Updated subtitle copy.
- New "Where claudemesh fits" section positioned between Features and
WhatIsClaudemesh: four-card comparison (vs MCP, vs subagents,
vs OpenClaw, and the positive claim) framing claudemesh as a wire
between Claude Code sessions, not a replacement for any of them.
All work is additive: 10 new files in apps/web/src/modules/marketing/
home/fake-claude-code/ plus hero-mesh-animation.tsx, hero-with-mesh.tsx,
and where-mesh-fits.tsx. Single edit each to features.tsx and
(marketing)/page.tsx to swap in the new hero and mount the new section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restores the curl installer script at /install. Adds:
- In-memory fetch counter (visible in container logs)
- Server-side PostHog event 'install_script_fetched' with
IP, user-agent, and referer (fire-and-forget)
- Console log per fetch for monitoring
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Editorial timeline with vertical track, colored phase markers,
2-column feature grids per milestone. Shows v0.1→v0.8 evolution:
Foundation → Groups → Shared Intelligence → Files → Data Platform
→ Platform. Anchored by '66 npm releases. Every feature below is
in production today.' Dashed 'next' card at bottom for roadmap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simplify getting-started to 2 steps: npm install + launch --join.
Remove "claudemesh install" section, update join page to show
launch --join as the primary flow, update invite format examples.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop /install route (curl|bash script). Install is just `npm i -g
claudemesh-cli`. Update hero, FAQ, getting-started, and join flow to
reflect the simplified 3-step onboarding: install → join → launch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- generateMetadata instead of metadata (getMetadata returns a function)
- Use TURBOPACK=0 env prefix instead of --no-turbopack flag (not recognized in Docker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new panels below the existing peer graph + live stream grid:
- StateTimelinePanel: vertical timeline of audit events and presence
status changes, auto-scrolling, sorted newest-first
- ResourcePanel: 2x2 card grid showing live peers, envelopes by
priority, audit event breakdown, and session status
Both share the same TanStack Query cache key as the existing panels
(no extra API calls). Matches the --cm-* dark terminal aesthetic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders peers as SVG nodes in a radial layout with animated edges
showing real-time message traffic. Shares the same TanStack Query
cache as LiveStreamPanel (same queryKey). Side-by-side on desktop,
stacked on mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Landing page copy was stuck at the v0.1 feature set (messaging + state + memory + groups).
The CLI now ships 43 MCP tools across 5 persistence backends. This commit brings the site
copy in sync with what's actually built.
Changes:
- Hero, features, pricing, FAQ, CTA, footer: reflect 43 tools, files, SQL, vectors, graphs
- Features section: expanded from 4 tabs to 7 (added Files, Database, Vectors)
- New /getting-started page: full install guide with correct 4-step flow
- New Mesh vs MCP section: side-by-side diagrams + 8-row comparison table
- Fix: install-toggle on /join page had `npx claudemesh@latest init` (init doesn't exist)
→ replaced with `curl -fsSL https://claudemesh.com/install | bash`
- Navigation: added Getting Started to header, footer, hero link
- COPY.md synced with all 6 capability areas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove ALL Payload imports, withPayload wrapper, and (payload)
routes. Blog index + changelog are now static data arrays.
Blog post at /blog/peer-messaging-claude-code is static TSX.
Payload CMS stays as a dev dependency for future local admin
but has zero presence in the production build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final working pattern: withPayload via require() for build
compatibility, admin page replaced with redirect (no RootPage
import = no React #130), payload packages externalized from
turbopack bundle. Blog/changelog use server-side getPayload().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
withPayload crashes ALL routes with React #130 in standalone
output — even with admin page replaced by redirect. The wrapper
injects a client-side ConfigProvider that fails hydration.
Removed: withPayload wrapper, entire (payload) route group.
Kept: payload.config.ts, migrations, blog/changelog server-side
queries with graceful DB fallback.
Payload admin runs on local dev only (add withPayload back in
next.config when running pnpm dev). Production content via
static TSX pages or future API-based publishing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep withPayload (needed for build compilation) but replace the
admin RootPage with a redirect. The RootPage's ConfigProvider
causes React #130 in standalone output. Blog/changelog use
server-side getPayload() which works fine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replicate working cuidecar Payload setup:
- require() instead of ESM import for withPayload
- routes.admin = "/payload" to avoid /admin conflicts
- (payload)/payload/ route group with own layout + importMap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Payload CMS integration crashes the entire production app — the
withPayload wrapper + admin routes break when DB tables don't
exist and the layout conflicts with i18n routing.
Keeping: payload.config.ts, blog/changelog pages with graceful
DB fallback, static blog post page. Payload admin will be added
back once properly integrated with a dedicated route group that
doesn't inherit the main app layout.
The blog post at /blog/peer-messaging-claude-code is static TSX
and works without Payload runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Static TSX page at /blog/peer-messaging-claude-code while Payload
admin is not yet configured in production. Full 1100-word post on
protocol, dev-channels, prompt-injection, and next steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Production has no SQLite — Payload pages now catch connection
errors and render empty state instead of crashing with React #130.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Payload's /api/[...slug] route that conflicts with existing
/api/[...route]. Blog/changelog pages use Payload's local API.
Includes cli install.ts backup + assertNoMcpLoss guards (from
worktree agent).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Landing page showed \`curl -fsSL claudemesh.sh/install | bash\` but
the domain didn't resolve, so anyone copy-pasting got a DNS error.
Ship:
- apps/web/src/app/install/route.ts: GET returns an auditable bash
installer (Node preflight, npm install -g claudemesh-cli, runs
claudemesh install, prints next steps, colored output). No Node
auto-install — fails clean if missing with a pointer.
- apps/web/src/proxy.ts: exclude /install from the i18n matcher so
Next.js returns the shell script unmangled.
- hero.tsx + features.tsx: swap claudemesh.sh → claudemesh.com.
Test: curl http://localhost:3000/install | bash -n → OK.
Content-Type: text/x-shellscript; charset=utf-8.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clickable HTTPS invite URLs replace the raw ic://join/<token> as the
primary share format. Someone receiving a link in Slack now lands on
a friendly page with install instructions, not a dead-end.
Backend:
- createMyInvite returns a new joinUrl field
(https://claudemesh.com/join/<token>) alongside the existing
ic://join/<token> inviteLink and raw token. Schema + Hono route
updated. ic:// scheme stays — CLI parses both.
- New GET /api/public/invite/:token in packages/api/src/modules/public/
(unauthed). Decodes the base64url payload, verifies ed25519
signature against owner_pubkey using the same canonicalInvite()
contract the broker enforces on join, then joins mesh/invite/user
to return the shape needed by the landing page. Does NOT mutate
usedCount — this is a read-only preview.
- Error taxonomy: malformed | bad_signature | expired | revoked |
exhausted | mesh_archived | not_found. Each returned with any
metadata we CAN surface (meshName, inviterName, expiresAt) so the
error page can be specific ("ask Jordan for a new one").
- cache-control: public max-age=30 on valid invites, no-store on
errors (reasons flip as state changes).
Frontend:
- New public route /[locale]/join/[token] (no auth). Server
Component fetches the preview endpoint, branches on valid/invalid,
renders a minimal landing-design-language shell (wordmark header,
clay accents, serif headlines, mono commands).
- Valid-invite view: "You're invited to {meshName}", inviter +
role + member-count lede, install-toggle component.
- Invalid-invite view: per-reason error copy + inviter name when
available + link back to /.
- InstallToggle client component: three-way state
(unknown/yes/no). Asks "first time / already set up?", then shows
either the 3-step install+init+join path with per-step copy
buttons, or the single claudemesh join <token> command for users
who have the CLI. Every code block has copy-to-clipboard.
- Security footer: "ed25519 keypair generated locally, you keep
your keys, broker sees ciphertext only, leave anytime with
claudemesh leave <mesh-slug>".
Invite generator (/dashboard/meshes/[id]/invite):
- QR code now encodes the HTTPS joinUrl instead of ic:// (phone
cameras land on the web page → friendly path).
- Primary CTA copies the HTTPS URL. Secondary "Copy CLI command"
for fast-path users. Footer explanation updated.
CLI coordination note: dispatched to broker/db lane — claudemesh CLI
needs to accept BOTH ic://join/<token> AND
https://claudemesh.com/join/<token> (extract <token> from pathname).
Server side already returns both.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Live social-proof counter on the landing page, tied to the E2E
narrative. Formatted as understated mono footer, not hero brag.
Backend — new GET /api/public/stats (unauthed, 60s in-memory cache):
{
messagesRouted: SELECT COUNT(*) FROM mesh.message_queue,
meshesCreated: SELECT COUNT(*) FROM mesh.mesh WHERE archivedAt IS NULL,
peersActive: SELECT COUNT(*) FROM mesh.presence WHERE disconnectedAt IS NULL,
lastUpdated: ISO timestamp,
}
Aggregate counts only — no ids, no names, no ciphertext, no routing
metadata. Safe for public consumption. cache-control header sets
public/s-maxage=60 for edge caching. `x-cache: HIT|MISS` for debug.
Frontend — new MeshStats Server Component at
modules/marketing/home/mesh-stats.tsx. Reads the endpoint server-side
via the ~/lib/api/server client, renders monospace footer:
ciphertext routed → 4,217 messages · 12 meshes · 8 peers online
broker sees none of it
Graceful zero state: when messagesRouted === 0 shows
"ciphertext → ready to route" instead of embarrassing zeros. Tabular-
nums for the numeric spans so they don't jitter across renders.
Mounted between <CallToAction /> and <LatestNewsToaster />. Page-level
`export const revalidate = 60` so Next.js ISR refreshes the counter
every minute without a DB hit on every request (combined with the
API cache = two-layer 60s TTL, DB sees ~1 query/minute).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Anthropic design language is icon-only, no emoji. User flagged that
claude-intercom components (and copy I wrote) were leaning on emoji
decoration. Swept all user-visible emojis in apps/web + packages/ui.
Changes:
- meshes/new onboarding banner: "Welcome to claudemesh 👋" → drop the
wave, text stands alone
- meshes/[id]/invite banner: "🎉 Mesh created" → "Mesh created"
- demo-dashboard script message: "thanks 🙏" → "thanks." (inline prose)
- MeshStream message-type chips: replaced the ⟐ / ← / → unicode
glyphs with proper inline SVG icons (10×10 stroke paths). Each chip
now carries: plus-sign for broadcast, up-arrow for hand-raise,
right-arrow for direct. Same claude-orange / emerald / neutral
coloring, same typography — just geometry instead of text symbols.
Nothing swapped to Lucide React imports yet — Icons barrel in
packages/ui/web only exports a subset (Circle, Check, MessageCircle,
Sparkles, Megaphone), and the four glyphs we needed were simpler as
inline SVG than adding barrel exports + per-component import plumbing.
If emoji→Lucide fully lands, we'll add the rest to the Icons barrel
in one pass.
Skipped per PM spec: TTS announcements, commit messages, code
comments, logs — not user-visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mesh detail page at /dashboard/meshes/[id]:
- Header: flex-col → flex-row at sm breakpoint. Live/Invite buttons
stretch full-width stacked on mobile (flex-1), auto-width side-by-
side from sm up.
- "Generate invite link" truncates to "Invite" on mobile (viewport
constrained) so the button fits next to Live.
- Members + active-invites rows: stack metadata vertically on mobile
(flex-col → sm:flex-row), wrap badges inside with flex-wrap so the
member display-name + role + revoked badges don't horizontal-scroll.
Invites list at /dashboard/invites:
- Wrap the table in overflow-x-auto with min-w-[560px] on the table
itself. 5-column data-table that genuinely needs horizontal space
— don't fake it with card stacking, let the user scroll naturally.
Quick-send composer deferred to a follow-up — writes a message to the
mesh, which requires a client-side encryption decision (ed25519
keypair in the browser? key derivation from session? plaintext-to-
broker and break E2E?). Parked as its own spec.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wires the Discord-style demo UI to real user data. Users with 1+ meshes
now get situational awareness: who's online, what's in the queue, what
the broker saw recently — polling every 4s, all E2E encrypted.
Extraction pass:
- New `<MeshStream peers messages channelLabel footer>` renderer at
modules/marketing/home/mesh-stream.tsx — pure presentation, no
playback engine, no data fetching. Handles peer filter, hover-for-
ciphertext tooltip, animated message list.
- demo-dashboard.tsx refactored to use it: keeps the playback loop,
traffic-light chrome, and script-driven messages; passes everything
to MeshStream via props. ~120 LOC shorter.
Backend:
- new GET /api/my/meshes/:id/stream in packages/api (same authz gate
as /my/meshes/:id — owner OR non-revoked member). Returns:
- up to 20 live presences (disconnectedAt IS NULL), joined to
meshMember for displayName
- up to 50 most-recent message_queue envelopes with metadata only:
sender + displayName, targetSpec, priority, createdAt, deliveredAt,
byte size, and a 24-char ciphertext preview (this IS what the
broker sees — no plaintext anywhere in the response)
- up to 20 recent audit events
- getMyMeshStreamResponseSchema in schema/mesh-user.ts matches exactly.
Frontend:
- new LiveStreamPanel client component at modules/mesh/live-stream-panel.tsx
— react-query with refetchInterval: 4000ms, refetchIntervalInBackground
false. Maps presences + envelopes to MeshStream's Peer/Message shape,
classifies targetSpec into message type ("tag:*" → ask_mesh, "*" →
broadcast, else direct). Passes through the ciphertextPreview as the
hover content — no fake ciphertext in live view.
- new route /dashboard/meshes/[id]/live with server-side authz preflight
via /my/meshes/:id. Mounts LiveStreamPanel inside a dashboard page
shell with breadcrumb back to mesh detail.
- Mesh detail page gets a new "Live" pill button (clay-pulsing dot)
next to "Generate invite link" in the header.
- paths config gets dashboard.user.meshes.live(id).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Visitors read the page and still don't grok claudemesh is a *mesh* of
agents, not chatbot integrations. Fix: drop them straight into a live
Discord-style view of 4 peers talking. No auth, no WS, no backend —
a pre-recorded 10-second conversation that loops, encrypted over a
fake broker.
The conversation script (demo-dashboard-script.ts) hits every mental
model the landing needs to plant:
bob-desktop → #payments: "stripe sig verification broken?"
alice-laptop (self-nominates): "hit this 2wks ago, pulling fix"
alice → bob (direct): "<actual fix with file+line>"
bob → alice: "saved me. thanks 🙏"
carol-ios → #infra: "CI red on main?"
bob → carol: "reverting 7af3d, ~2min"
Covers: tag-routed broadcast (ask_mesh), self-election (hand-raise),
direct-peer DM, cross-surface (phone peer in the mix), multi-thread
concurrency.
Component (demo-dashboard.tsx, ~420 LOC):
┌─────────────────────────────────────────────────┐
│ meshes | peers | live message stream │
│ side | list | (motion fade+rise on each msg) │
│ bar | | │
└─────────────────────────────────────────────────┘
- requestAnimationFrame playback loop against SCRIPT[].t offsets
- Auto-loops after SCRIPT_DURATION_MS, 4s pause baked in
- Per-peer filter: click a peer in the sidebar, only their messages
show in the stream (from OR to), shows "filtered: <peer>" in header
- Play / pause / restart buttons
- Hover any message → dashed clay box shows the fake ciphertext:
"broker sees only this: AUp3+n7z1bY=.kQfM9vL4jR8..." — drives the
E2E point without a paragraph of crypto copy
- Status dots: green idle, clay pulse working, grey offline
- Surface glyphs inline (terminal / phone / slack) next to peer names
- Message type chips: ⟐ broadcast, ← hand-raise, → direct
- Progress bar at bottom ties the loop to a visible timeline
- Window chrome with traffic-light dots + "mesh.claudemesh.com ·
flexicar-ops · 4 peers online" header
Mounted between WhatIsClaudemesh and BeyondTerminal — explainer
first, then show-don't-tell.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes the "chatbot integration" misread of the landing page by framing
claudemesh as a mesh-not-a-bridge above the gateways section.
- Mental shift (before/after): one Claude per project → mesh of Claudes,
mesh-as-substrate with surfaces tapping in
- Three concrete use cases with honest limits: solo multi-machine,
cross-repo team (Alice's Stripe fix / Bob rediscovers), mobile 3am
oversight via WhatsApp gateway
- Inline SVG architecture diagram: broker at center ("routes only · never
decrypts"), six peers hexagon-orbiting with ciphertext edges
- Anti-framing "what claudemesh is NOT" list to kill misreads
- Italic pull-quote closer with the honest one-liner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strategic positioning upgrade. claudemesh was framed as terminal-to-
terminal — which is only half the story. The broker is protocol-
agnostic: any peer with an ed25519 keypair joins the mesh, so the mesh
can reach WhatsApp bots, Telegram, iOS apps, Slack, email gateways,
browser extensions. Terminal is ONE client, not THE client.
New section at /#beyond: "Your mesh. Any surface." — 6 gateway cards
(Terminal / WhatsApp / Telegram / iOS·Android / Slack / Email) with
honest status badges:
- shipping → Terminal only (what we have today)
- on the roadmap → WhatsApp, Telegram, iOS/Android (we will build)
- build it yourself → Slack, Email (open protocol, community territory)
No overclaiming: we don't pretend WhatsApp is live. The honest framing
is exactly the aspirational hook — the architecture is there, the hooks
exist, someone could build a gateway peer today.
Each card has a custom 28px inline SVG glyph in clay, short serif
description, and a status chip. Grid staggers in with Motion.
Footer CTA: "the protocol is open · ed25519 + libsodium · build a gateway
for anything" + link to /#protocol on GitHub.
Hero subhead reworked to hint at cross-surface: "Peer mesh for Claude —
reachable from anywhere you are. … Terminal is one client, not THE
client."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auth routes (/login, /register, /forgot-password, /update-password, /join)
were rendering with the default Geist fonts + shadcn neutral palette +
turbostarter SVG logos — completely off-brand against the marketing
landing. User reported from production.
Rewire auth/layout.tsx to:
- use --cm-bg / --cm-fg / --cm-clay tokens (dark #141413)
- Anthropic Sans for UI, Anthropic Serif for the right-aside tagline
- claudemesh wordmark (mesh glyph + serif) in place of Icons.Logo /
Icons.LogoText
- right aside: mesh glyph + serif tagline "Every Claude Code session,
woven into one mesh." + description paragraph, matching the CTA
copy from the landing
- subtle orange radial glow on the aside for depth
Inner form components (BetterAuth password/social buttons) pick up the
tokens from globals.css, so the forms look native on the dark layout
without per-component rewrites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New user signs in → /dashboard (user) → hits server-side getMyMeshes → 0
results → redirects to /dashboard/meshes/new?onboarding=1. Create-mesh
page renders a welcome banner explaining what a mesh is. After submit,
if ?onboarding=1 was set, the form bounces to
/dashboard/meshes/[id]/invite?onboarding=1 instead of the mesh detail
page. Invite page renders a "🎉 Mesh created" banner with the
`claudemesh join <link>` CLI snippet.
The onboarding flag is URL-driven — no persistence needed, dismissal
happens naturally when the user navigates away.
Also rewrites the /dashboard (user) home page from the placeholder
"Welcome to your Dashboard" TurboStarter card grid to a claudemesh-
native view: top 6 meshes with badges, All meshes / New mesh CTAs.
Removes the unused Card/Icons imports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two visible launch-polish issues:
1. BuyCtaDialog popup was firing on an exponential backoff schedule
(15s, 30s, 60s, …) pushing users toward turbostarter.dev/#pricing +
Discord. Wrong product, wrong audience. Fully removed: mount point
in [locale]/layout.tsx + the component file + localStorage keys will
self-prune on next visit.
2. WhatsApp/Slack/Twitter link previews were pulling the TurboStarter
boilerplate opengraph-image.png (from Jan 8). Replaced with a 1200×630
claudemesh OG: "CLAUDEMESH" pixel wordmark left side, hero mesh
composition (6 Claude Code terminals + pixel-crab hub + orange
energy lattice + vaporwave grid floor) right side, "peer mesh for
Claude Code sessions" tagline in mono beneath wordmark.
3. Default metadata description swapped from the dangling
`common:product.description` i18n key (which rendered as the key
itself because the key doesn't exist in our trimmed translations)
to a hardcoded claudemesh description.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>