memo.matters.ai is a standalone Astro app that lets owners host static HTML+CSS+JS projects and share them through one-off links with investor-grade access controls. Think DocSend for arbitrary HTML, where every link can require a different combination of email allowlist, OTP, password, NDA, watermark, and per-session screen recording.
The app is colocated under apps/memo in the Matters monorepo, but it is otherwise standalone — its own Clerk application, its own Postgres + Mongo + R2 + Upstash projects, its own Vercel deployment, its own visitor-side JWT.
| Route | Owner / Visitor / Machine | Description |
|---|---|---|
/ | Public | Landing page + sign-in CTA |
/sign-in, /sign-up | Owner | Clerk-hosted, fully themed |
/u/<username> | Owner | Project grid |
/u/<username>/projects/<id> | Owner | Monaco + live preview + Generate-with-Claude |
/u/<username>/projects/<id>/{activity,performance,utilization,links} | Owner | Per-project analytics tabs |
/u/<username>/projects/<id>/collaborators | Owner | Manage co-owners + invites for a shared project. See Collaboration |
/u/<username>/agreements | Owner | NDA library with Tiptap editor |
/u/<username>/settings | Owner | Profile + Clerk account + PATs + OAuth connections + recording config |
/u/<username>/settings/tokens | Owner | Create, scope, revoke Personal Access Tokens |
/u/<username>/settings/connections | Owner | Revoke OAuth-connected apps (claude.ai, etc.) |
/u/<username>/people/<emailHash> | Owner | Cross-link timeline for one email — every visit by this person across every link the owner controls. See Observability → People page. |
/<slug> | Visitor | Auto-redirects to the next required gate |
/<slug>/{email,otp,password,nda} | Visitor | Gate steps |
/<slug>/ | Visitor | The hosted HTML, streamed from R2 with watermark + beacon injected (canonical trailing-slash URL; /<slug>/view is a 308 redirect kept for legacy bookmarks) |
/<slug>/raw/<path> | Visitor | Per-asset proxy, re-checks gate before serving |
/api/owner/* | Owner | Clerk-gated owner API |
/api/v/<slug>/* | Visitor | JWT-gated visitor API (form submits) |
/api/beacon | Visitor | Telemetry sink (sendBeacon target) |
/api/recording/logrocket | Visitor | Round-trips LogRocket replay URLs onto the visits row |
/api/owner/ai/generate | Owner | Streaming Anthropic-backed code generation (Generate with Claude) |
/api/owner/tokens, /api/owner/connections/<id> | Owner | PAT + connection management |
/u/<username>/spaces, /u/<username>/spaces/<id> | Owner | Data-room dashboard — list + detail (engagement, recent visits + replay, access policy, inline manage/settings). See Spaces |
/u/<username>/projects/<id>/qa | Owner | In-viewer Q&A answer panel |
/s/<spaceSlug>/ | Visitor | Branded data-room index; gate at /s/<spaceSlug>/{email,otp,password,nda} |
/s/<spaceSlug>/p/<projectId>/ | Visitor | A project's HTML, streamed under the space gate (link slug never exposed) |
/api/owner/{spaces,teams,diligence,qa}/* | Owner | Data-room CRUD (Spaces, Teams & Diligence, Q&A) |
/api/oauth/<provider>/{connect,callback} | Owner | Cloud-storage connect — Drive/Dropbox/Box (Integrations) |
/api/mcp | Machine | Streamable HTTP MCP server. PAT or OAuth bearer. |
/oauth/authorize, /oauth/token, /oauth/revoke, /oauth/register | Machine | OAuth 2.0 + PKCE + Dynamic Client Registration |
/.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource | Machine | OAuth discovery |
/api/cron/{rotate-jwt-key,expire-links,render-signed-pdf} | System | Vercel + QStash jobs |
/health | System | 200 OK liveness check |
| Layer | Choice | Why |
|---|---|---|
| Framework | Astro 5 (SSR) + React 19 islands | Visitor pages ship as zero-JS HTML; owner area gets islands for Monaco, drawers, charts |
| Hosting | Vercel (@astrojs/vercel) | Native cron support, serverless functions |
| Owner auth | Clerk (@clerk/astro) | Matches monorepo convention |
| Visitor auth | jose HS256 JWT, daily-rotated key in Upstash | Zero third-party in the visitor critical path |
| Password hashing | argon2id + per-row salt + env pepper | Industry standard |
| Postgres | Neon, Drizzle ORM, schema memo | Owners, projects, links, agreements, signatures, visits, audit logs |
| Time-series DB | MongoDB Atlas, time-series collection events (90-day TTL) | Visitor telemetry fanned via the beacon endpoint |
| Object storage | Cloudflare R2 (S3-compatible) | Staging + immutable version prefixes |
| Cache / RL / Q | Upstash Redis + QStash | OTP, ratelimits, daily JWT key, signed-PDF jobs |
| Resend | OTP delivery | |
| Session replay | LogRocket | Per-session replay URLs persisted to visits.logrocket_session_url |
| Heatmaps | Microsoft Clarity | Aggregate engagement signal |
| Error tracking | Sentry (@sentry/astro) | Standard monorepo pattern |
| Editor | Monaco via @monaco-editor/react | Lazy-loaded React island |
| Compile pipeline | Native esbuild on publish | Compiles .ts/.tsx to sibling .js, no npm install per project |
| Rich-text | Tiptap (StarterKit) | Used for agreement bodies |
| Maps | react-simple-maps | Visitor world map on Performance tab |
@sparticuz/chromium-min + playwright-core | Signed-NDA PDF rendering, fired off via QStash |
pnpm --filter @matters/memo dev # http://localhost:4004
Without Clerk keys the dev server boots and serves the landing page (/) + health endpoint (/health), but /sign-in and any owner route returns a Clerk error. With everything set, all features work locally except cron triggers (those run via Vercel or QStash in prod).
/settings/tokens. OAuth 2.0 + PKCE with Dynamic Client Registration so claude.ai's "Add custom MCP" works one-click. MCP server at /api/mcp with 13 tools across projects, files, publish, links, visits. In-editor TS compile-on-save via esbuild-wasm with a sandboxed live-preview iframe. Generate with Claude assistant in the editor (Anthropic streaming).lucide-react icons. Folder tree with collapsible directories + per-file unicode glyphs. Multi-file tabs with dirty tracking + ⌘W close / ⌘Shift+[ ⌘Shift+] cycle. Find-in-files with regex + case-sensitive toggles via a debounced /api/owner/projects/<id>/search. Changes panel showing added/modified/removed files vs the current published version, click → opens in Monaco's DiffEditor (left: published, right: staged). ⌘P quick-open fuzzy file picker. Drag-resize sidebar + status bar (Ln:Col, language, dirty). Image preview with Raw byte toggle. Default-file pick: README.md > *.md > index.html > *.html > *.txt. Download draft / Download v{N} as ZIP.<section data-memo-section="…"> instrumentation through IntersectionObserver. Persistent-visitor cookie (memo_vp, 365-day rolling) stitches sessions across the 24h gate-JWT lifecycle. Engagement score column on every visit (formula combines completion + log-duration + section breadth). Four behavioural signals (skim, deep_read, return, share_detected) — first two emitted inline by the beacon, last two by a 10-minute cron. People timeline page at /u/<u>/people/<emailHash> joining every visit by one email across every link. Live activity feed polling every 8s with yellow-fade animation for new rows. Capture-at-mint geo from Vercel headers. All documented in detail at Observability.project_members + project_invites, with requireProjectAccess (owner OR member) replacing the single-owner check across ~30 routes/pages and an accessibleProjectIds "shared with me" dashboard; a Collaborators tab per project. Live co-editing on a transport-agnostic Yjs layer (Liveblocks or Cloudflare Durable Objects, selected by MEMO_REALTIME_PROVIDER) — multi-cursor Monaco via MonacoBinding + presence avatars, gated off by default for zero single-user regression, persistence riding the existing save/publish path. All documented at Collaboration.requireProjectAccess, invites, "shared with me") + transport-agnostic Yjs live co-editing (Liveblocks / Cloudflare Durable Objects)./<slug>.ANTHROPIC_API_KEY, cloud-storage client ids, PROVIDER_TOKEN_ENCRYPTION_KEY), crons (now 4: rotate-jwt-key, expire-links, dispatch-webhooks, derive-cross-visit-signals), the single-baseline Drizzle migration (0000_init.sql + drizzle-legacy/), Vercel project setup, and the verify pipeline (type-check + vitest + build + route smoke).