Phase 1 of v0.3.0 — replaces the regex-on-decoded-ciphertext scan
in /v1/notifications and the dashboard MentionsSection with reads
from a new mesh.notification table populated at write time.
Schema 0025: mesh.notification (id, mesh_id, topic_id, message_id,
recipient_member_id, sender_member_id, kind, created_at, read_at)
with a unique (message_id, recipient) so a re-fanned message yields
one row per recipient. Backfills existing v0.2.0 messages by
regex-matching the (still-base64-plaintext) bodies — guarded with
a base64 + length check so binary ciphertext doesn't crash the
migration.
Writers (POST /v1/messages + broker appendTopicMessage) now
extract @-mentions from either an explicit `mentions: string[]`
on the request OR a regex over the base64 plaintext (transitional
fallback). Targets are intersected with the mesh roster + capped
at 32 per message. Web chat panel sends the explicit array now so
it keeps working after phase 2 lands.
Readers switch to JOIN-on-notification:
/v1/notifications — table-backed, supports ?unread=1
POST /v1/notifications/read — new, mark by ids or all-up-to
MentionsSection (RSC) — same JOIN, returns readAt for each row
GET /v1/notifications also gains a read_at field per row so a
future bell UI can show unread vs read.
Once per-topic encryption (phase 2) lands, the regex fallback
becomes a no-op for v2 messages — clients MUST send `mentions`,
which they already do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Old next-block listed dashboard (shipped), slack bridge (still
v0.3.0), self-host (v0.3.0), SSO (out of scope). Replaces with
the actual roadmap horizon: daemon redesign, per-topic crypto,
self-host packaging, federation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Blog post "Agents and humans in the same chat" walks through what
shipped in the v1.7.0 demo cut: topics, REST gateway, real-time
SSE, mentions, notification feed, humans-as-peers. Linked from
the blog index above the original protocol post.
Demo script lays out a five-scene 90-second screen capture: two
terminal agents talking, dashboard topic list, live chat with
@-mention autocomplete, mentions feed cross-platform, close.
Production notes + distribution checklist included.
Marketing screenshots and the actual recording are still TODO.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Universe dashboard gets a "Recent mentions" section listing every
topic_message from the last 7 days that references the viewer via
`@<displayName>` (per-mesh — a user can carry different display
names in different meshes). One union'd OR query, capped at 20.
Each mention card links straight into the topic chat at the right
mesh. Snippet is the first 240 chars of the decoded ciphertext with
@-tokens highlighted in clay, matching the in-chat renderer.
GET /v1/notifications mirrors the same scan for api-key-authed
clients (CLI, bots) — accepts ?since=<ISO> for incremental polling.
Both paths use Postgres regex on the decoded base64 plaintext;
when per-topic encryption lands in v0.3.0 they'll move to a
notification table populated at write time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A "search" toggle in the chat header opens a small input that
client-filters loaded messages by plaintext match on body or
sender name. Live tail auto-scroll suspends while a query is
active so matches stay visible when new messages arrive.
Server-side fulltext search lands when ciphertext moves to
per-topic symmetric keys in v0.3.0 — until then there's no
server index to query, and the loaded window (last 100 plus
forward stream) covers most "find that thing from earlier"
needs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Typing `@` in the compose box opens a dropdown of matching mesh
members fed by /v1/members. Filters live by displayName prefix
(case-insensitive); online members rank above offline; shorter
names rank higher; capped at 8 entries.
Keyboard: ArrowUp/Down to navigate, Enter or Tab to insert,
Escape to dismiss. Mouse hover updates the selection; mousedown
inserts (mousedown so the textarea doesn't lose focus first).
Rendered messages now highlight @mentions in clay so they're
visually distinct from plain text — same regex the autocomplete
uses, so the round trip is consistent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A revoked api key or missing topic returned by GET /v1/.../stream
used to throw inside the catch and bounce through the backoff loop
forever. Now any 4xx response terminates the loop and surfaces the
status + body in the panel error so the user sees the real cause.
5xx and network errors still reconnect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /v1/members lists every non-revoked member of the api key's
mesh, decorated with online state from presence rows. Distinct from
/v1/peers (active sessions) — sidebars want roster + live dot, not
just whoever is currently connected.
Chat panel splits into a 2-column layout (>=lg) with a 180px
sidebar that polls the roster every 20s. Online members go up top
with status-coloured dots (idle=green, working=clay, dnd=fig);
offline members fade below at 50% opacity. Bots get a "bot" tag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Universe page aggregates unread topic_message rows per mesh for the
viewing user. Counts messages newer than topic_member.last_read_at
(or all messages if the viewer never opened the topic) and excludes
anything the viewer authored. One JOIN-grouped query, not N+1.
Mesh card surfaces the count as a clay-rounded badge to the left of
the role chip — matches the per-topic badge style on the mesh detail
page so unread is the same visual idiom across the dashboard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PATCH /v1/topics/:name/read upserts topic_member.last_read_at for the
api key's issuing member. The chat panel calls it on mount and on
every inbound SSE message (5s debounce so we don't hammer it).
GET /v1/topics now returns unread per topic — counts messages newer
than last_read_at and not authored by the viewer. Mesh detail page
shows a clay-rounded badge next to each topic name with the count
(99+ ceiling).
AuthedApiKey gains issuedByMemberId so endpoints can attribute
side-effects to the minting member. Required because external api
keys aren't tied to a specific peer member; only dashboard- and
CLI-minted keys carry one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /v1/topics/:name/stream opens an SSE firehose, polled server-side
every 2s and streamed as `message` events. Forward-only — clients
hit /messages once for backfill, then live from connect-time onward.
Heartbeats every 30s keep the connection through proxies.
Web chat panel reads the stream via fetch + ReadableStream so the
bearer token stays in the Authorization header (EventSource can't
set custom headers, which would force token-in-URL leaks). Auto-
reconnect with exponential backoff. setInterval polling removed.
Vercel maxDuration bumped to 300s on the catch-all API route so
streams aren't cut at the 10s default.
drizzle migrations/meta/ deleted — superseded by the filename-
tracked custom runner in apps/broker/src/migrate.ts (c2cd67a).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two UX wins for the v0.2.0 chat surface:
- Mesh cards on /dashboard now show topic count alongside members and
tier ("3 MEMBERS · 2 TOPICS · FREE"). Active topics render in clay,
zero in tertiary. One aggregate query, not N+1.
- Mesh detail page replaces the CLI-hint empty state with an inline
CreateTopicForm. Non-empty topic lists get a compact "+ new topic"
pill in the section header. Server action validates name format
(lowercase letters/digits/dashes, 1-50 chars), inserts via the
unique (meshId, name) index, auto-subscribes the creator as topic
lead, then redirects into the chat.
Sidebar audit — kept platform/manage/dev structure as is. Topics are
mesh-scoped so a top-level "topics" entry would have nothing to land
on without a mesh chosen first. Discoverability lives on the mesh
cards instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit against peer-graph-panel, live-stream-panel, state-timeline-panel,
and resource-panel showed the chat used generic shadcn Card chrome
instead of the established panel pattern. Refactor swaps the wrapper
to the canonical idiom:
- rounded-[var(--cm-radius-lg)] + border-[var(--cm-border)] + bg-[var(--cm-bg)]
- mono header strip with clay-pulse fetch dot, 11px label, 10px metadata
- mono 9px footer status bar (mesh slug · poll cadence · key expiry)
- Anthropic Mono via var(--cm-font-mono) on chrome, sans on message body
- compose textarea uses cm-bg-elevated + cm-border-hover focus state
- error line in cm-fig (#c46686) instead of generic destructive
No behavior change — only chrome. Polling, send path, decode logic
unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New dashboard route at /dashboard/meshes/[id]/topics/[name] gives signed-in
users a thin chat client over the v0.2.0 REST surface. The mesh detail page
now lists topics with one-click links into the chat. Backend layout:
- packages/api/src/modules/mesh/api-key-auth.ts — exports
createDashboardApiKey() that mints a 24h read+send key scoped to a single
topic for the caller's member id. The page server component calls this on
every render and embeds the secret in the props of the client component;
the secret never touches sessionStorage so a tab close = key effectively
abandoned (the row remains until expiresAt).
- apps/web/.../topics/[name]/page.tsx — server component, NextAuth gate,
resolves the user's meshMember.id, mints the key, renders the shell.
- apps/web/src/modules/mesh/topic-chat-panel.tsx — client component, polls
GET /v1/topics/:name/messages every 5s, sends via POST /v1/messages.
Encoding wraps base64(plaintext) into the ciphertext field — matches the
current broker contract until per-topic HKDF lands in v0.3.0.
The mesh detail page gains a Topics section with empty-state copy that
points users at the CLI verb (claudemesh topic create) for now; topic
creation from the web UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /dashboard landing that surfaces meshes and invitations-to-you
in one view. Replaces the simple mesh grid at /dashboard (preserved
at /dashboard/legacy).
Backend additions:
- GET /api/my/invites/incoming — pending_invite rows addressed to
the authed user's email, joined with invite for role + expiry and
user/mesh for display. Unaccepted + unrevoked + unexpired only.
- DELETE /api/my/invites/incoming/:id — dismiss a pending invite
(revokes the pending_invite row only; underlying invite code stays
valid so the inviter can re-send).
Web additions (all under apps/web/src/modules/dashboard/universe/):
- welcome.tsx — editorial serif header with mesh + invite counts
- invitations.tsx — client card with Accept (→ /i/:code claim flow)
and optimistic Decline
- meshes-grid.tsx — hero card + compact grid, linked to mesh detail
- reveal.tsx — fade-up motion matching marketing _reveal.tsx
Styling uses the existing claudemesh design tokens (--cm-clay,
--cm-bg-elevated, Anthropic Sans/Serif/Mono) — nothing redefined.
Onboarding redirect (0 meshes → /meshes/new?onboarding=1) preserved,
now gated on 0 invitations too so users with pending invites still
land on the dashboard.
Sidebar icon switched to Atom for the "universe" concept.
Standalone prototype saved at prototypes/live-dashboard.html for
reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- info/inbox commands → unified render.ts
- install route: drop in-memory counter, rely on PostHog + structured logs
- docs: roadmap, CLAUDE.md reflect alpha.31 state
- tests workflow now also builds + smoke-tests the CLI bundle
- homebrew tap bootstrap kit in packaging/homebrew-tap-bootstrap/
(README + copy of the formula template for dropping into the tap repo)
- upstream Claude Code issue draft for rich <channel> UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
Email (broker):
- Rebrand mesh-invitation.tsx to match site (clay accent #d97757,
cream fg, Anthropic Serif/Mono, dark bg). Mesh glyph in header.
- Hero CTA links to the /i/short URL landing page.
- Single one-liner 'npm i -g claudemesh-cli && claudemesh launch --join URL'
so new users copy once, paste once, done.
Web InstallToggle:
- Replace two-step numbered list with single one-liner in the first-time
panel. Reduces copy/paste ops from 2 to 1 and stops prescribing
'YourName' as a literal (CLI now defaults to $USER).
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>
Payload CMS imports .css/.scss/.svg files that Node.js ESM can't handle
during page data collection. Added a custom ESM loader that stubs these
asset imports, fixing the build that has been broken since the upgrade.
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>
Header now checks session and shows avatar + name + Dashboard link
when logged in, instead of always showing Sign in / Start free.
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>
Rewrites pricing section from single "public beta" card to side-by-side
hosted vs self-hosted comparison reflecting the cleaner product
architecture. Enterprise sell is now concrete: "Run our Docker image,
point your CLI at it, done — your mesh never leaves your VPC."
Updates hero subtitle, CTA, FAQ, and where-mesh-fits claim card to
reinforce the two deployment modes consistently across the landing.
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>
Completes the v2 invite user experience. The generator now ships two
delivery modes behind a simple Link | Email toggle, and the vestigial
ic:// scheme is gone from every user-visible surface.
Modes
- Link (default, existing flow): mints a v2 invite, displays short URL
+ QR + CLI command. No behavioral change vs wave 2.
- Email (new): admin types a recipient email, submit dispatches through
the POST /api/my/meshes/:id/invites/email endpoint (wave 2), which
mints a normal v2 invite, records a pending_invite row, and stubs the
Postmark send with a TODO. Result card shows a "✓ Invite sent to X"
banner plus the same QR card so the admin can also share manually.
Honest UX copy on the stub:
"Email delivery is stubbed in v0.1.x — the invite is valid. Share the
link directly if needed." Avoids pretending something shipped that
hasn't.
ic:// cleanup
- inviteLink field no longer rendered or stored (still returned by the
API for backward compatibility; just not surfaced)
- CLI command now copies `claudemesh join <code>` (falls back to
shortUrl when code is null), matching the new v2 entry point
- Zero remaining `ic://` references in the UI
Implementation notes
- Two separate useForm instances (linkForm, emailForm) with dedicated
resolvers and submit handlers — clearer state boundaries than
conditional validation on a merged schema
- Mode toggle uses role="group" + aria-pressed, focus-visible ring,
keyboard-navigable
- Email result banner is role="status" for screen readers
- RPC client has one `as any` on `(api.my.meshes[":id"].invites as any)
.email.$post` — the endpoint IS registered server-side (wave 2) but
the monorepo's Hono type regen is out-of-band; TODO comment marks the
cast for removal when the RPC types catch up
- No new deps
- Component export signature unchanged
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>
Missing from standalone output → invite creation crashes with
'Cannot find module libsodium-wrappers' in production.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
turbopack.rules only applies to turbopack. When building with
TURBOPACK=0 (required for Payload CMS), webpack has no SVG rule.
Icons.UnitedKingdom returns an object → React #130. Adding a
webpack config rule for @svgr/webpack fixes both bundler paths.
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>
Node ESM can't handle .css imports during Next.js route collection.
This loader intercepts .css resolutions and returns empty modules,
fixing the build for all Payload deps (richtext-lexical, react-image-crop, etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: Next.js 16 defaults to Turbopack for builds, but Payload CMS's
richtext-lexical imports .css files that fail during route collection in
Node ESM context.
Fix: add @payloadcms/richtext-lexical and @payloadcms/next back to
serverExternalPackages so Next.js skips their internal imports during
route collection. Use --webpack explicitly since Turbopack production
builds are incompatible with Payload (payloadcms/payload#14786).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>