Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
5631286
Add agent collaborators design spec
alcor Aug 30, 2026
d1a65a3
Add connect UI section to agent collaborators spec
alcor Aug 30, 2026
c803d89
Rename worker to vapor and route vapor.fyi as custom domain
alcor Aug 30, 2026
3588d03
Add agent collaborators implementation plan
alcor Aug 30, 2026
2d6a7fe
Trigger CI
alcor Aug 30, 2026
cdb4bdd
Add shared agent protocol module
alcor Aug 30, 2026
12efca4
Add Yjs markdown block layer with content-hash anchors
alcor Aug 30, 2026
1d96511
Reuse critic-constants delimiters and nest overlapping marks
alcor Aug 30, 2026
5fae152
Serve documents at the root path
alcor Aug 30, 2026
34f2e01
Add per-document agent token roster
alcor Aug 30, 2026
2773142
Clear agent_tokens on document expiry
alcor Aug 30, 2026
73cf40c
Add agent read and mutation RPCs with anchor checks
alcor Aug 30, 2026
6cc81d2
Fix agentReplace silently no-opping delete on inverted to<from range
alcor Aug 30, 2026
2bbc906
Add performance engine for paced agent edits
alcor Aug 30, 2026
bdb10cf
Fix stale index/position and mid-typing eviction duplication in perfo…
alcor Aug 30, 2026
3b71b2f
Add synthetic agent presence to awareness
alcor Aug 30, 2026
743c87b
Add document events with mention detection and long-poll
alcor Aug 30, 2026
a7b82b6
Fix thread_reply over-firing, missing rate limits, and error code ove…
alcor Aug 30, 2026
539eab4
Serve MCP at /mcp backed by DocumentAgent RPCs
alcor Aug 30, 2026
a351ac3
Add raw markdown export and MCP help page
alcor Aug 30, 2026
7b8a56f
Fix unescaped MCP help origin and add nosniff to markdown export
alcor Aug 30, 2026
e583b55
Add invite agent dialog and roster management
alcor Aug 30, 2026
3628c0e
Fix invite agent dialog accessibility
alcor Aug 30, 2026
4c9e0d0
Redirect secondary domains to vapor.fyi
alcor Aug 30, 2026
95349a5
Update README and CLAUDE.md for the vapor fork
alcor Aug 30, 2026
dd57c88
Redirect pre-rename /docs/:id links to the root path
alcor Aug 30, 2026
597a6f9
Enforce the reserved root-slug list
alcor Aug 30, 2026
422d163
Make agent writes survive unsupported markup and corrupt rows
alcor Aug 30, 2026
60454da
Fix typed @mentions, agentless digests, and event fan-out
alcor Aug 30, 2026
8a55385
Guard create_document input and cap the agent roster
alcor Aug 30, 2026
54f5efe
Validate before charging rate limit, and refresh last_seen_at for any…
alcor Aug 30, 2026
0607be0
Merge branch 'alcor/deploy-vapor-fyi' into alcor/agent-collaborators
alcor Aug 30, 2026
c64788f
Broadcast agent-origin updates to connected clients
alcor Aug 30, 2026
077e0b7
Rename user-visible strings from mist to vapor
alcor Aug 30, 2026
ae4c574
Rename theme and frontmatter keys from mist to vapor
alcor Aug 30, 2026
d5126e1
Spec anonymous agents: tokenless MCP with auto-enrolled identity
alcor Aug 30, 2026
e37b9a3
Allow tokenless MCP sessions with auto-enrolled anonymous agents
alcor Aug 30, 2026
fd3a556
Derive create_document's minted agent name from clientInfo too
alcor Aug 30, 2026
dcbdb45
Ignore .superpowers scratch directory
alcor Aug 30, 2026
c80c2a0
Add identity phase design spec
alcor Aug 31, 2026
d34f7a5
Make /mcp the authenticated door, /mcp/anonymous the tokenless one
alcor Aug 31, 2026
e6dd4aa
Retire per-doc tokens in identity spec; identity becomes the credential
alcor Aug 31, 2026
c027aa0
Add identity phase implementation plan
alcor Aug 31, 2026
c3a9450
Port session and Google auth core from subpixel
alcor Aug 31, 2026
6806a90
Add anonymous animal identities with Noto Emoji presence
alcor Aug 31, 2026
9aebefd
Add global Registry durable object for profiles and OAuth state
alcor Aug 31, 2026
b2da398
Add auth routes for Google sign-in sessions
alcor Aug 31, 2026
b50f673
Add OAuth 2.1 authorization server for MCP clients
alcor Aug 31, 2026
a7a7193
Replace per-doc tokens with verified identity; two MCP doors; web sig…
alcor Aug 31, 2026
b37475a
Document the identity model and two MCP doors
alcor Aug 31, 2026
a6dcb1b
Set GOOGLE_CLIENT_ID var for production sign-in
alcor Aug 31, 2026
b0e87d4
Add CIMD client support; fix sign-in presence, attribution, avatars, …
alcor Aug 31, 2026
7adf305
Add /privacy and /terms pages
alcor Aug 31, 2026
5ef93c9
Vary anonymous identities beyond Anonymous with a persistent adjective
alcor Aug 31, 2026
65496d9
Fix duplicate comment threads; attribute agents as their owner's Agent
alcor Aug 31, 2026
cd5b205
Label anonymous agents Agentic <Animal>
alcor Aug 31, 2026
c84e2c3
Use the owner's first name for signed-in agent labels
alcor Aug 31, 2026
cf51825
Merge pull request #2 from arfct/alcor/agent-collaborators
alcor Aug 31, 2026
94755fe
Rewrite README around what vapor is now
alcor Aug 31, 2026
9b6ccfb
Use neutral example identities in code, tests, and specs
alcor Aug 31, 2026
46968ad
Rewrite README in document voice
alcor Aug 31, 2026
8533d9a
Merge pull request #3 from arfct/alcor/agent-collaborators
alcor Aug 31, 2026
2e210c7
Cut README back to plain facts
alcor Aug 31, 2026
67e2e2b
Merge pull request #4 from arfct/alcor/readme-voice
alcor Aug 31, 2026
392b0ff
Redesign toolbar menus, comment cards, and mark underlines
alcor Aug 31, 2026
907014d
Add vector logo as primary favicon
alcor Aug 31, 2026
774a4c7
Simplify the comment rail and fold Preview into the mode menu
alcor Aug 31, 2026
ce89399
Scroll the highlighted phrase into view instead of quoting it
alcor Aug 31, 2026
eb0ab20
Merge pull request #5 from arfct/alcor/ui-refresh
alcor Aug 31, 2026
ca951d2
Plan the notes-app import: WYSIWYG, toolbar, UI kit, styles, roadmap
alcor Aug 31, 2026
0b7a2fc
Fold data-model evaluation into the WYSIWYG plan
alcor Aug 31, 2026
aaf3907
Add Base UI component kit; migrate menus off Radix
alcor Aug 31, 2026
1320c9b
Import the notes app's editor type scale and block styles
alcor Aug 31, 2026
c79d7c4
WYSIWYG editing: rich nodes in the CRDT, markdown at the boundary
alcor Aug 31, 2026
0d115ca
Add the formatting toolbar
alcor Aug 31, 2026
ae97340
Declare secret-backed Env keys so typegen matches CI
alcor Aug 31, 2026
2ec5e36
Plan sleeping tabs and DO duration cost control
alcor Aug 31, 2026
63e2c77
Free-tier safeguards: sleeping tabs and DO wake hygiene
alcor Aug 31, 2026
4473d24
Plan the MCP Events polyfill
alcor Sep 1, 2026
76bcde7
Add DO usage guardrail script; mark plan phases shipped
alcor Sep 1, 2026
db991c2
Declare node globals for the usage script
alcor Sep 1, 2026
e286080
Reorder the header; expose the dev server on the tailnet
alcor Sep 1, 2026
bf6a0bc
Drop the chevrons from the Edit and Share triggers
alcor Sep 1, 2026
b4a5898
Merge pull request #6 from arfct/alcor/notes-import-plans
alcor Sep 1, 2026
cadb2a7
Merge pull request #7 from arfct/alcor/sleeping-tabs-plan
alcor Sep 1, 2026
ad70f85
Read the analytics token from CLOUDFLARE_ANALYTICS_TOKEN
alcor Sep 1, 2026
c716e54
Make webhook policy safe for set-and-forget subscribers
alcor Sep 1, 2026
d47d6dd
Merge main (WYSIWYG engine, safeguards) into the events branch
alcor Sep 1, 2026
94595f9
Implement the MCP Events polyfill
alcor Sep 1, 2026
a68036c
Add the vapor-to-routine mention relay
alcor Sep 1, 2026
4c70c23
Merge pull request #8 from arfct/alcor/mcp-events-polyfill-plan
alcor Sep 1, 2026
47fbf1f
UI polish: header start-editing button, avatar initials, icon and cop…
alcor Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Fathom analytics (optional — omit to disable)
VITE_FATHOM_SITE_ID=
VITE_FATHOM_DOMAINS=

# Identity (optional — omit to keep sign-in disabled). SESSION_SECRET signs
# session JWTs (Workers secret in prod, `openssl rand -base64 32` locally);
# GOOGLE_CLIENT_ID is the public Google OAuth client id for GSI sign-in.
SESSION_SECRET=
GOOGLE_CLIENT_ID=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ worker-configuration.d.ts

# Coverage
/coverage/
.superpowers/
83 changes: 66 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This repo follows the Artifact Primer: https://github.com/arfct/ops/tree/main/primer

## Start of Session
- Standards (style, commits, branches): https://github.com/arfct/ops/blob/main/primer/standards.md
- Work tracking: Linear workspace `arfct`, team Artifact (A) — https://github.com/arfct/ops/blob/main/primer/linear.md
- Bugs: https://github.com/arfct/ops/blob/main/primer/bugs.md · Deployment: https://github.com/arfct/ops/blob/main/primer/deployment.md
- Agent conventions and boundaries: https://github.com/arfct/ops/blob/main/primer/agents.md

## This repo

vapor is a collaborative markdown editor — a fork of [mist](https://github.com/inanimate-tech/mist), deployed at https://vapor.fyi. `npm run dev` for local development, `npm run deploy` (with `CLOUDFLARE_ACCOUNT_ID` set) to ship to Cloudflare Workers. This is a fork: keep upstream's build tooling (ESLint config, CI) unchanged unless upstream changes it — don't propose tooling swaps here.

### Start of Session

Read project documents to load context:

- `docs/design-system.md` — visual design, typography, colours, layout
- `docs/technical-architecture.md` — platform, framework stack, directory structure, critical rules
- `docs/plans/2026-08-30-agent-collaborators-design.md` — agent collaborators spec (tool surface, performance engine)
- `docs/plans/2026-08-30-identity-design.md` — identity phase spec (Google sign-in, MCP OAuth, counterpart agents)

Also check `plans/` for any active plan.

## Project Overview
### Project Overview

vapor is a collaborative markdown editor — a cross between GitHub Gist and Google Docs. Users can quickly share and do multiplayer editing on markdown documents in real-time. Everything is public by URL. Sign-in (Google) is optional and adds identity/attribution, never a wall. Documents persist live with no save button. Documents auto-expire after 99 hours. AI agents can join documents as human-like collaborators over MCP (see "Agent collaborators" below).

MIST is a collaborative markdown editor — a cross between GitHub Gist and Google Docs. Users can quickly share and do multiplayer editing on markdown documents in real-time. Everything is public by URL (no auth yet). Documents persist live with no save button. Documents auto-expire after 99 hours.
Naming is "vapor" throughout: `APP_NAME`, page titles, the export frontmatter key (`vapor:`), and the theme localStorage key (`vapor-theme`).

## Tech Stack
### Tech Stack

- **Backend:** Cloudflare Workers + Durable Objects (SQLite storage)
- **Frontend:** React Router 7 (SSR) + Cloudflare Agents SDK
Expand All @@ -24,15 +37,15 @@ MIST is a collaborative markdown editor — a cross between GitHub Gist and Goog
- **Language:** TypeScript (strict mode)
- **Testing:** Vitest with v8 coverage

## Prerequisites
### Prerequisites

Requires Node.js 22+ (see `.nvmrc`). Before running commands:

```bash
source ~/.nvm/nvm.sh && nvm use
```

## Commands
### Commands

```bash
npm run dev # Local development server
Expand All @@ -51,29 +64,48 @@ npx vitest run tests/unit/lib/critic-parser.test.ts
npx vitest run -t "pattern"
```

## Architecture
### Architecture

See `docs/technical-architecture.md` for full details.

### Directory Layout
#### Directory Layout

- `agents/` — Server-side Durable Object agents (currently just `DocumentAgent`)
- `agents/` — Server-side Durable Object agents: `DocumentAgent` (document state), `VaporMcp` (MCP server), `Registry` (global identity + OAuth state)
- `app/components/` — React UI components
- `app/lib/` — Editor logic, CriticMarkup, Yjs provider, utilities
- `app/shared/` — Constants and types shared between client and server
- `app/routes/` — File-based routing (`home.tsx`, `docs.$id.tsx`, `new.ts`)
- `app/routes/` — File-based routing (`home.tsx`, `doc.$id.tsx`, `new.ts`)
- `workers/app.ts` — Cloudflare Worker entry point
- `workers/routes.ts` — Pure handlers for `/:id.md` and the `/mcp` help page
- `tests/` — Unit tests (`tests/unit/`) and integration tests (`tests/integration/`)

### Import Path Alias
#### Routes

Documents render at the root path, not under `/docs`:

| Route | Handler |
|---|---|
| `/` | `home.tsx` |
| `/new` | `new.ts` |
| `/:id` | `doc.$id.tsx` |
| `/:id.md` | `workers/routes.ts` — raw markdown export |
| `/mcp` | `agents/mcp.ts` (`VaporMcp`) — OAuth-gated MCP server |
| `/mcp/anonymous` | `agents/mcp.ts` (`VaporMcp`) — tokenless MCP server |
| `/auth/*` | `workers/routes.ts` — Google sign-in sessions |
| `/oauth/*`, `/.well-known/oauth-*` | `workers/oauth.ts` — OAuth 2.1 AS for MCP |
| `/agents/*` | `agents/document.ts` (`DocumentAgent`) — Yjs WebSocket |

Root slugs share one namespace with a small reserved-word list (`app/shared/constants.ts`); the id generator and the `/:id` loader both guard against collisions.

#### Import Path Alias

`~` resolves to `app/` (configured in tsconfig and vitest). Use `~/lib/foo` instead of relative paths.

### Critical Rule: Server/Client Separation
#### Critical Rule: Server/Client Separation

Client-side React components must **never** import from `agents/`. The `agents` package uses `cloudflare:` protocol imports that don't exist in the browser. Use `app/shared/` for types needed by both sides.

### Real-Time Collaboration Flow
#### Real-Time Collaboration Flow

The multiplayer system works as follows:

Expand All @@ -82,7 +114,7 @@ The multiplayer system works as follows:
3. **TipTap** uses `@tiptap/extension-collaboration` (bound to the Yjs doc's `XmlFragment`) and `@tiptap/extension-collaboration-caret` for cursor awareness.
4. **Worker entry** (`workers/app.ts`) — `routeAgentRequest()` intercepts `/agents/:agent/:name` requests before React Router handles the rest.

### CriticMarkup / Suggest Mode
#### CriticMarkup / Suggest Mode

Track-changes functionality spans multiple files:

Expand All @@ -92,13 +124,30 @@ Track-changes functionality spans multiple files:
- `app/lib/critic-serializer.ts` — Serializes marks back to CriticMarkup delimiter syntax
- `app/lib/critic-markup.ts` — TipTap extension that wires up the CriticMarkup marks and delimiter decorations

### Testing Constraints
#### Agent collaborators

AI agents connect as MCP clients and edit through the same CriticMarkup/Yjs machinery humans use, with a performance engine that paces their typing to look human. Full design: `docs/plans/2026-08-30-agent-collaborators-design.md`.

- **`VaporMcp`** (`agents/mcp.ts`) — an `McpAgent` (Cloudflare Agents SDK) served at `/mcp`. Stateless per document: each tool call names a `doc_id` and forwards to that doc's `DocumentAgent` via DO-to-DO RPC. Tool schemas and definitions live in `agents/mcp-tools.ts`.
- **`DocumentAgent`** (extended) — owns the agent roster, performance queue, and event log alongside the Yjs doc; all mutations happen inside the DO that owns the document. Agent RPCs take a verified `AgentIdentity` (principal or anonymous) and enroll it into the roster on first touch — there are no per-doc tokens.
- **`workers/routes.ts`** — pure (no `cloudflare:` imports) handlers for `GET /:id.md`, the MCP help page, and `/auth/*` sign-in, wired into `workers/app.ts`.

#### Identity (Google sign-in + MCP OAuth)

Ported from subpixel's dependency-free auth stack. Full design: `docs/plans/2026-08-30-identity-design.md`.

- **`app/lib/auth.server.ts`** — Google ID-token verification (WebCrypto), HMAC session JWTs, the `vp_session` cookie. Identity is a principal (`email:<addr>`); sign-in is optional.
- **`agents/registry.ts`** (`Registry` DO, one `"global"` instance) — profiles, counterpart agent slugs, and OAuth clients/codes/refresh tokens.
- **`workers/oauth.ts`** — OAuth 2.1 AS (PKCE, dynamic registration, discovery). Access tokens are 1-hour session JWTs carrying the granted capabilities; the consent page (`app/lib/oauth-pages.ts`) is where write is granted. `/mcp` requires one of these; `/mcp/anonymous` needs none.
- Secrets: `SESSION_SECRET` (Workers secret), `GOOGLE_CLIENT_ID` (public var). See `.dev.vars.example`.

#### Testing Constraints

- The `agents` package uses `cloudflare:` imports — it **cannot** be imported in plain Vitest. Test agent logic through integration tests or mock the imports. Unit tests should focus on pure logic in `app/lib/` and `app/shared/`.
- Coverage thresholds ramp linearly from 0% to 80% between Feb–Dec 2026 (see `vitest.config.ts`).
- Tests live in `tests/unit/` and `tests/integration/`, mirroring the source structure.

### ESLint Conventions
#### ESLint Conventions

- Unused variables must be prefixed with `_` (e.g., `_args`, `_ctx`).
- Tagged template expressions are allowed (for `this.sql` in Durable Objects).
108 changes: 40 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,67 @@
# mist
# vapor

Collaborative markdown editor. A cross between GitHub Gist and Google Docs — share and do multiplayer editing on markdown documents, quickly.
You paste a draft into chat and now there are two copies, both going stale. vapor gives the draft one URL instead: a live markdown document anyone can open and edit, people and AI agents side by side, each with a cursor. It deletes itself after 99 hours.

Everything is public by URL. Documents persist live with no save button. Multiple users see each other's cursors in real time.
Running at [vapor.fyi](https://vapor.fyi). A fork of [mist](https://github.com/inanimate-tech/mist).

## Features
## Documents

- **Real-time multiplayer editing** via TipTap + Yjs, backed by Cloudflare Durable Objects
- **Live markdown formatting** — inline styles render as you type, with formatting characters shown in grey
- **Suggest mode** — track changes using CriticMarkup (additions, deletions, comments, highlights)
- **Threaded comments** with highlight anchoring
- **Preview mode** — rendered markdown with click, hover, or keypress toggle
- **CLI upload** — `curl https://your-domain/new -T file.md`
- **Drag and drop** `.md` files to create new documents
- **Dark/light/auto themes**
- **Documents auto-expire** after 99 hours
Anyone with the URL can read and edit. Live markdown with track changes (CriticMarkup), comments anchored to highlights, and a rendered preview. No accounts required, no save button, nothing kept past 99 hours—export before then.

## Tech stack

- [Cloudflare Workers](https://developers.cloudflare.com/workers/) + [Durable Objects](https://developers.cloudflare.com/durable-objects/) (backend + persistence)
- [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/) (real-time WebSocket agent)
- [React Router 7](https://reactrouter.com/) (SSR)
- [TipTap 3](https://tiptap.dev/) (editor)
- [Yjs](https://yjs.dev/) (CRDT for multiplayer)
- [Tailwind CSS 4](https://tailwindcss.com/) (styling)
- TypeScript, Vitest
```bash
curl https://vapor.fyi/new -T notes.md # create from a file
curl https://vapor.fyi/<id>.md # raw markdown back
```

## Getting started
## People and agents

### Prerequisites
Sign-in (Google) is optional and only changes attribution:

- Node.js 22+ (see `.nvmrc`)
- A Cloudflare account (free tier works)
| | Human | Agent |
|---|---|---|
| Anonymous | Curious Ladybug 🐞 | Agentic Butterfly 🦋 |
| Signed in | Ada Lovelace | Ada's Agent |

### Setup
Your anonymous animal lives in localStorage and follows you between documents. Sign in and your name takes over, earlier comments included.

```bash
git clone https://github.com/inanimate-tech/mist.git
cd mist
npm install
```
## Connecting an agent

### Development
vapor is an [MCP](https://modelcontextprotocol.io) server. Two ways in:

```bash
npm run dev
```
# signed in: stable identity, write access if you grant it
claude mcp add --transport http vapor https://vapor.fyi/mcp

### Deploy
# anonymous: no setup, suggest and comment only
claude mcp add --transport http vapor https://vapor.fyi/mcp/anonymous
```

Set your Cloudflare account ID via environment variable:
Agents get suggest and comment by default; full write is a separate grant on the consent screen. Their edits type in at human pace with a visible cursor (`pace: "instant"` skips the show). Mention `@agent-name` in a document to wake an agent waiting on `await_events`.

```bash
export CLOUDFLARE_ACCOUNT_ID=your-account-id
npm run deploy
```
Tools: `read_document` · `insert` · `replace` · `suggest` · `comment` · `reply` · `join` · `leave` · `await_events` · `create_document`. Each document's Agents panel lists who's enrolled, with revoke.

### Optional: Analytics
## How it's built

To enable [Fathom](https://usefathom.com/) analytics, set these environment variables (or add to `.dev.vars`):
Each document is one Cloudflare Durable Object holding the [Yjs](https://yjs.dev/) doc, agent roster, and event log. [TipTap](https://tiptap.dev/) and [React Router 7](https://reactrouter.com/) on the front, the [Agents SDK](https://developers.cloudflare.com/agents/) underneath, and a dependency-free auth stack (Google sign-in, OAuth 2.1 with PKCE and CIMD) ported from [subpixel](https://subpixel.app).

```
VITE_FATHOM_SITE_ID=your-site-id
VITE_FATHOM_DOMAINS=your-domain.com
agents/ Durable Objects: DocumentAgent, VaporMcp, Registry
app/ React Router app
workers/ Worker entry, routes, OAuth server
tests/ Unit + integration
```

### Commands

```bash
npm run dev # Local development server
npm run build # Production build
npm run deploy # Build and deploy to Cloudflare Workers
npm run typecheck # TypeScript type checking
npm run lint # ESLint
npm run test # Vitest with coverage
npm run test:watch # Vitest in watch mode
```
## Developing

## Project structure
Node 22+.

```
agents/ Durable Object agents (server-side document state)
app/
components/ UI components
lib/ Editor logic, utilities, CriticMarkup, Yjs provider
routes/ File-based routing
shared/ Types and constants shared between client and server
workers/ Cloudflare Worker entry point
tests/ Test suite
```bash
npm install
npm run dev # local server
npm run test # also: typecheck, lint
npm run deploy # needs CLOUDFLARE_ACCOUNT_ID
```

## Licence
Sign-in needs `GOOGLE_CLIENT_ID` (a wrangler var) and `SESSION_SECRET` (a Workers secret); both optional in development. See `.dev.vars.example`. Design docs live in [docs/plans/](docs/plans/).

[MIT](LICENSE)
[Privacy](https://vapor.fyi/privacy) · [Terms](https://vapor.fyi/terms) · [MIT](LICENSE)
Loading