Payload CMS v3.81 withPayload() requires Next.js >=16.1.0 for
production turbopack builds. Upgrade resolves the build failure.
Reverts the dev-only withPayload workaround — now loads normally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Payload CMS v3.81 withPayload() injects a turbopack config key
that Next.js 16.0.10 rejects in production builds (needs >=16.1).
Load withPayload only in dev; production gets a pass-through.
Payload admin works locally; production serves blog/changelog
as regular Next.js pages querying the Payload API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Next.js 16.0.10 fails production builds with turbopack config
present (needs >=16.1.0). Gate it behind NODE_ENV !== production.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover encryptDirect/decryptDirect with three scenarios (happy path,
wrong recipient, tampered ciphertext) and invite link parsing with
round-trip, expiry rejection, and malformed input handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The private setStatus(ConnStatus) conflicted with the public
setStatus("idle"|"working"|"dnd") method, causing TS2393 under strict
typecheck. Rename the private one to setConnStatus and update all
internal call sites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three surgical edits for credibility:
Hero subheadline: remove WhatsApp/Slack/phone promises (roadmap,
not shipped), replace "reachable from anywhere you are" (vague)
with concrete value prop: E2E encrypted, delivered mid-turn as
<channel> reminders, broker never sees plaintext. Change "Free
and open-source. Forever." → "Open-source CLI. Free during
public beta." to match the pricing section.
Logo bar: remove Vercel/Linear/Stripe/Supabase/Shopify/Figma
(not actual customers). Replace with tech stack labels: Claude
Code, MCP, libsodium, Bun, TypeScript, MIT.
FAQ: fix "Is claudemesh free?" to match beta pricing. Fix "How
do I get started?" to reference the real curl installer instead
of nonexistent npx claudemesh init. Fix "Which Claude Code
versions?" to name actual install + launch flow.
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>
The 6-tier grid was selling features that don't exist yet:
- Pro \$12/mo: dashboard, peer registry, message history (not built)
- Plus \$24/mo: Tailscale mesh (already default), MCP bridge (free),
audit log (not built)
- Team \$99/mo: \"self-hosted broker\" AND \"25 peers\" AND
\"unlimited peers\" — three contradictions in one tier
- Business \$499/mo: multi-region, retention, Slack/Linear (roadmap)
- Enterprise: claimed \"SOC 2 pack\" without certification
Replaced with a single Public-Beta card:
- Free, no card required
- Two columns: Shipping today (verified against source) + Roadmap
v0.2–v0.3 (clearly labeled)
- Promise: \"Beta users keep the free plan for life\"
Non-additive rewrite of a shipped section. Authorized by user
explicitly; required because the prior pricing created refund +
legal risk.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Running \`claudemesh\` with no args now detects install state and
prints context-appropriate guidance: suggests \`install\` if MCP
not registered, \`join\` if no meshes, \`launch\` if ready.
Replaces the static HELP dump with a first-run wizard that meets
users where they are.
Static HELP still available via --help.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three Tier-2 polish commands for debugging + discoverability:
- claudemesh --version / -v: print CLI version (baked from
package.json at build time via Bun JSON import).
- claudemesh status: WS-probe each joined mesh's broker, report
reachability per mesh. Exit 1 if any broker unreachable.
- claudemesh doctor: run 6 preconditions — Node>=20, claude on PATH,
MCP registered, hooks registered, config file parses + chmod 0600,
mesh keypairs validate. Each check has a pass/fail + fix hint.
Exit 0 if all pass.
Help text now leads with version (\"claudemesh v0.1.3 —\").
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Additive NEWS entry pointing to the new public repo
github.com/alezmad/claudemesh-cli and the launch command.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `claudemesh launch [args]` that spawns Claude Code with
--dangerously-load-development-channels server:claudemesh so peer
messages arrive as <channel> system reminders mid-turn instead of
pull-only via check_messages. Windows uses shell:true to resolve
claude.cmd from PATHEXT.
Prints an info banner before spawning that explains the channel's
scope (peer text injection only), the trust model (treat as
untrusted input), and that existing tool-approval prompts remain
the safety net. --quiet skips the banner.
Install output now mentions `claudemesh launch` as the recommended
launch path; plain `claude` still works for pull-only mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The push handler previously fell through to base64-decoding the
raw ciphertext whenever decryptDirect() returned null. For direct
(crypto_box) messages that produces garbage binary which surfaces
as garbled bytes in Claude's <channel> reminder. Limit the base64
fallback to legacy broadcast/channel messages (no senderPubkey),
and emit a clearer "⚠ message from <pubkey> failed to decrypt"
warning when direct decryption fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same bug as cuidecar/cccb43a: \`overscroll-behavior: none\` applied to
the universal selector killed wheel events on every overflow:hidden
container on the page — hero, demo-dashboard, cta, surfaces, anything
with rounded cards. Combined with the mesh-stream overflow-y-auto
(fixed in 701516b) this was double-trapping the wheel.
Move the rule from \`*\` to \`html\`, change to \`overscroll-behavior-y\`.
Still prevents rubber-band chaining at the document level, but lets
wheel events propagate naturally through nested overflow containers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The demo-dashboard embedded MeshStream with a fixed min-h-[480px] grid
+ overflow-y-auto on the message <ol>. Browsers capture every wheel
event that fires over a scrollable container — so hovering the demo
section froze page scroll until the user moved the cursor off.
Landing demo has only 6 messages, never needs internal scroll. The
fixed viewport only makes sense in the live dashboard where envelope
count can exceed the box.
Added `scrollable?: boolean` prop to MeshStream (default false).
- demo-dashboard (landing): no prop → intrinsic height, no overflow,
wheel events propagate to the page
- live-stream-panel (/dashboard/meshes/[id]/live): scrollable → keeps
the chat-style fixed viewport with scroll
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full parity with claude-peers:
1. Push-injection (the "tap on shoulder" UX)
- MCP server now declares experimental.claude/channel capability
- BrokerClient onPush handlers emit server.notification({
method: "notifications/claude/channel",
params: { content, meta: {from_id, from_name, mesh_slug,
mesh_id, priority, sent_at, delivered_at, kind}}
})
- Claude Code injects each push as <channel source="claudemesh">
system reminder, so the receiver session sees inbound messages
WITHOUT calling check_messages manually
- Updated MCP instructions with the "RESPOND IMMEDIATELY" framing
(adapted from claude-peers)
2. Status hooks in install (default-on, --no-hooks to opt out)
- new apps/cli/src/commands/hook.ts: reads stdin JSON (Claude Code
hook payload), extracts cwd+session_id, POSTs /hook/set-status
to every joined mesh's broker in parallel with process.ppid +
1s timeout per POST. Silent fail, fire-and-forget.
- install.ts: writes to ~/.claude/settings.json registering
`claudemesh hook idle` on Stop + `claudemesh hook working` on
UserPromptSubmit. Idempotent, preserves other hook entries.
- uninstall.ts: removes both hook entries + MCP entry; leaves
unrelated hook/MCP entries alone.
- dedupes by brokerUrl (multiple meshes on same broker → one POST)
3. CLI surface
- new subcommand: `claudemesh hook <status>` (internal, but
exposed so Claude Code can invoke it via the hook shell command)
- `install --no-hooks` for users who want bare MCP registration
- --help updated
Coexistence with claude-peers: both tools register Stop and
UserPromptSubmit hooks, each POSTs to its own broker. Claude Code
fires multiple hooks per event without conflict.
npm version 0.1.0 → 0.1.1 (patch).
Verified:
- install with hooks → 2 entries added to settings.json ✓
- install --no-hooks → "Hooks skipped" ✓
- uninstall → both MCP entry + 2 hook entries removed ✓
- `echo '{...}' | claudemesh hook idle` with no joined meshes →
silent no-op ("no joined meshes, nothing to do") ✓
- MCP initialize response includes experimental.claude/channel ✓
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs caught via devtools on live site:
**1. CSP 'font-src 'self' data:' violation × 3 per landing load.**
BaseLayout was loading Geist + Geist_Mono via next/font/google. In
prod builds Next.js self-hosts those under /_next/static, but the
generated CSS still references `--font-sans: "Geist", …` which some
browsers resolve by re-requesting fonts.gstatic.com. Since we ship
Anthropic Sans/Serif/Mono self-hosted already (/fonts/*.woff2 via
@font-face in globals.css), the Geist dependency was pure overhead.
Removed `next/font/google` imports entirely. Added a `.cm-root`
class on <html> that remaps the Tailwind `--font-sans/--font-mono`
tokens to our `--cm-font-sans/--cm-font-mono` vars — so every
Tailwind `font-sans` / `font-mono` utility now resolves to Anthropic
families. No Google Fonts fetch, no CSP violation.
**2. /pricing 401 on public visit.**
`<Plan>` calls `useCustomer()` → `GET /api/billing/customer` which
needs auth. Unauthed visitor on /pricing → 401 in devtools + wasted
round trip. Gated `useCustomer` on `authClient.useSession()` —
query `enabled: !!session?.user`. Public visitors now skip the fetch
entirely; signed-in users still get their customer record.
**3. Residual "Welcome back! 👋" on /auth/login (both locales).**
Emoji sweep (e91fc80) missed the i18n translation files. Removed 👋
from en/auth.json + es/auth.json login header titles.
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>
Pairs with claudemesh-2's new /join/[token] landing page. Users can
now paste a clickable HTTPS URL instead of the dev-only ic:// scheme.
apps/cli/src/invite/parse.ts — new extractInviteToken() handles
four input formats before handing the raw base64url token to the
existing parseInviteLink pipeline:
- https://claudemesh.com/join/<token> (primary, clickable)
- https://claudemesh.com/<locale>/join/<token> (i18n prefix)
- ic://join/<token> (still supported, dev)
- <raw-token> (last resort: bare base64url)
User-facing strings updated to the HTTPS form:
- cli help: "join <url>"
- install success message
- list (no-meshes) hint
- MCP server "no meshes" error
- README.md primary example
- docs/QUICKSTART.md Path A + Path B
Verified extractInviteToken() on all 4 formats — each returns the
same base64url token → same broker /join lookup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ScrollContainer — the wrapper under every dashboard/admin SidebarInset
— had zero horizontal padding on its scroll child, so pages rendered
edge-to-edge against the viewport. On wide screens content also
stretched to whatever width the sidebar left over (no max-width).
Single-point fix: wrap the scroll child in
<div class="mx-auto w-full max-w-[var(--cm-max-w)] px-4 py-6 md:px-8 md:py-8">
Hits every route under SidebarInset in one change:
- /dashboard
- /dashboard/meshes + /new + /[id] + /[id]/invite + /[id]/live
- /dashboard/invites
- /dashboard/settings (+ billing, security)
- /admin + /admin/users, /organizations, /customers, /meshes,
/sessions, /invites, /audit
px-4 → md:px-8 matches the marketing sections' gutter rhythm.
max-w-[var(--cm-max-w)] (90rem) caps content on ultra-wide.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
apps/broker/scripts/load-test.ts — configurable harness (N peers ×
M msgs). Each peer gets a real ed25519 keypair, signs its own hello,
encrypts every send via crypto_box. Measures send→ack latency
(broker queue write) and send→push latency (full e2e round-trip).
Samples broker RSS + FD count via ps/lsof if BROKER_PID is set.
docs/LOAD-TEST-v0.1.0.md — honest baseline results:
- ≤ 10 peers × 100 msgs: sub-second p99, 100% delivery
- 25-100 peers × 100 msgs: 5-10s p99, 100% delivery, no FD leaks
- 100 peers × 1000 msgs (100k total): 23s p99, 88.8% delivery at
15min drain cap. Peak RSS 1156MB, max FDs 122.
Broker is DB-bound — bottleneck is fanout amplification (every send
triggers N drain queries across connected peers). Document this
honestly as where v0.1.0 tops out. Real production traffic is
orders of magnitude lighter than this burst test (human/AI cadence,
not synthetic burst) — launch-ready as-is.
v0.2 optimization targets documented in the report:
- fanout decoupling (batch drains on timer)
- drop refreshStatusFromJsonl from delivery hot path
- pipelined acks
- horizontal sharding by meshId
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>
Strategic positioning split for v0.1.0:
- Local/single-machine self-host → redirect to claude-intercom (MIT,
simpler, purpose-built for that case)
- Cross-machine / team → hosted claudemesh.com (E2E encrypted, zero-ops)
- Building the broker from source is an audit/fork path, not the
primary self-host flow. Enterprise self-host packaging deferred to
v0.2+.
Previous "Run your own broker" section pushed users toward a docker
pull + self-host flow we're not publishing images for this launch
(ghcr.io/alezmad/claudemesh-broker stays as future enterprise path).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
.gitea/workflows/release.yml runs on any v-prefixed tag push (and on
workflow_dispatch with a manual tag input). Strips the v prefix, logs
in to ghcr.io via the GHCR_TOKEN repo secret, then runs the existing
publish-images.sh → all 3 multi-arch images land with :<tag> + :latest
tags.
Workflow path from future releases:
git tag v0.1.1
git push --tags gitea-vps v0.1.1
→ 10 min later: ghcr.io/alezmad/claudemesh-*:0.1.1 + :latest live.
Inert until act_runner is installed on gitea-vps (post-launch decision
per ovhcloud-agutmou). Also serves as executable documentation for
forkers on Gitea/GitHub.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GHCR_TOKEN=ghp_xxx scripts/publish-images.sh 0.1.0 — logs into ghcr.io
as alezmad and pushes all 3 claudemesh-* images (broker + web + migrate,
multi-arch) via the existing build-multiarch.sh. Supports --dry-run
that prints what would publish without logging in or pushing.
When user drops their GHCR PAT, shipping the 0.1.0 image tag is one
command.
Also documents post-trim image sizes in DEPLOY.md Step 2 (broker 341MB,
migrate 653MB, web 250MB).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use pnpm deploy to flatten each package's runtime subset into /deploy,
then copy ONLY that into the runtime stage. Catalog + workspace:*
specifiers previously forced full-workspace resolution into every
image's node_modules — unnecessary for either runtime.
Results (arm64, same smoke tests pass):
- broker: 3.26GB → 341MB (-90%, drops all devDeps incl. drizzle-kit)
- migrate: 3.27GB → 653MB (-80%, keeps drizzle-kit which IS runtime)
Broker /health confirms GIT_SHA build-arg still propagates (gitSha:
"30bc24f" in smoke test). Migrate still reads drizzle.config.ts and
attempts the connection correctly.
--legacy flag needed because pnpm 10 defaults to inject-workspace-
packages mode which the monorepo doesn't opt into; legacy is safe here.
--ignore-scripts on deploy skips the root postinstall (sherif lint:ws)
which has nothing to do with runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User owns the alezmad github scope, not a claudemesh org — point README
+ build script + DEPLOY.md at the real namespace so the docker pull
snippets actually work on launch day. Image names are now
claudemesh-broker / claudemesh-web / claudemesh-migrate (prefixed since
they live under a personal scope).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
40-line block with docker run + curl /health verify + env var reference
+ build-from-source fallback pointing at scripts/build-multiarch.sh.
Sits between the architecture diagram and honest-limits section so OSS
adopters find it immediately after understanding the broker's role.
Links through to DEPLOY_SPEC.md for the full runtime contract.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claudemesh requires an account — mesh membership is tied to user.id.
e8ad7a5 flipped the config default but the env var override at
env.config.ts:43 still defaulted to true, keeping the button visible.
Fixed at env var level + example files. Needs Coolify rebuild since
NEXT_PUBLIC_* is build-time in Next standalone.
Four parallel jobs on push to main and on PRs:
- lint — pnpm lint (turbo across workspace)
- typecheck — pnpm typecheck (turbo across workspace)
- test-broker — pgvector/pg17 service container, drizzle-kit migrate,
then vitest on apps/broker (64 tests per DEPLOY_SPEC.md)
- build-amd64 — docker buildx build of broker + migrate + web images
for linux/amd64 (catches Linux-only Dockerfile bugs that Mac local
buildx can't hit reliably, closes the documented multi-arch followup)
All jobs use frozen-lockfile install + pnpm-store cache via setup-node.
Regenerates pnpm-lock.yaml to resolve apps/cli zod catalog drift that
was silently blocking any frozen-lockfile install (shipped under same
commit since CI cannot pass without it).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
scripts/build-multiarch.sh produces linux/amd64 + linux/arm64 image
manifests for all three deployable images. Mac devs (Apple Silicon)
pulling claudemesh images get arm64 native — no QEMU, no 2-4x startup
penalty, no warnings. VPS (amd64) gets the native variant from the
same manifest.
- 3 images in one script: broker, web, migrate
- Tags both <SHA> and :latest per image
- GIT_SHA build-arg wired in for broker /health provenance
Replaces scripts/build-and-push.sh which was hardcoded to a dead
registry (192.168.1.3:3030) and wrong org (alezmad/turbostarter).
DEPLOY.md Step 2 rewritten to use the new script + Mac Docker Desktop
Rosetta-emulation gotcha documented.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- switch email provider from resend (unused) to postmark (creds available)
- re-enable requireEmailVerification now that email path works
- env vars POSTMARK_API_KEY + EMAIL_FROM must be set in Coolify
Documents the v0.1.0 scope limit for the web dashboard and the v0.2
plan for turning the browser into a full mesh peer.
Context: quick-send composer was scoped into the mobile-responsive
pass but requires a client-side crypto decision. The correct design
is a WebCrypto-generated ed25519 keypair + IndexedDB storage so the
browser joins the mesh with the same security posture as the CLI,
not a second-class shortcut that breaks E2E. That's a 1-2 day build
(keypair gen, IndexedDB wrapper, crypto_box, signed hello, invite
redemption, key export UX) — out of scope for v0.1.0 launch.
v0.1.0 honest limit: dashboard = read-only situational awareness.
Messaging = CLI/MCP tools only.
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>
RESEND_API_KEY / SMTP credentials not yet configured in production.
Users sign up + land in dashboard immediately, no verification email.
Re-enable requireEmailVerification when email provider is live:
packages/auth/src/server.ts:93
README + FAQ claim MIT, but LICENSE.md was still the TurboStarter EULA
from the scaffold — mismatch is an HN/launch blocker. Replace with MIT
for claudemesh-authored code + Attribution section preserving scaffold
obligations.
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>