clickroom 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +365 -2
- package/bin/clickroom.js +293 -0
- package/cli/changes.js +148 -0
- package/cli/client.js +86 -0
- package/cli/cloud.js +100 -0
- package/cli/config.js +140 -0
- package/cli/deploy.js +71 -0
- package/cli/detect.js +155 -0
- package/cli/dev.js +279 -0
- package/cli/gaps.js +37 -0
- package/cli/login.js +74 -0
- package/cli/mcp.js +205 -0
- package/cli/note.js +109 -0
- package/cli/process.js +47 -0
- package/cli/push.js +198 -0
- package/cli/service.js +110 -0
- package/cli/share.js +350 -0
- package/cli/vapid.js +10 -0
- package/package.json +38 -4
- package/worker/handoff.js +351 -0
package/README.md
CHANGED
|
@@ -1,3 +1,366 @@
|
|
|
1
|
-
#
|
|
1
|
+
# clickroom
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Multiplayer review for **locally built prototypes**. Send a client a link and review together live: pins, region annotations, threaded comments, presence and cursors. Then hand the feedback to your coding agent over **MCP**, Markdown or JSON.
|
|
4
|
+
|
|
5
|
+
The app is a Cloudflare Worker that runs locally under `wrangler dev`. Clients reach it through Cloudflare's [local dev tunnels](https://developers.cloudflare.com/workers/local-development/local-dev-tunnels/): `--tunnel` opens a Quick Tunnel (`*.trycloudflare.com`), and `--tunnel-name` uses your named tunnel (a stable hostname that can sit behind Cloudflare Access). Durable Objects provide the realtime rooms and SQLite storage. Everything persists locally in `.wrangler/state`.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
client browser ──https──▶ trycloudflare / named host ──▶ wrangler dev (local)
|
|
9
|
+
├─ Worker: auth, routing, proxy
|
|
10
|
+
├─ assets: site/__ps (UI) + site/p/<name> (your builds)
|
|
11
|
+
├─ Registry DO: projects, share tokens, tunnel URL
|
|
12
|
+
└─ ProjectRoom DO per project: threads (SQLite) + WebSocket hub
|
|
13
|
+
coding agent ──stdio──▶ clickroom mcp ──▶ admin API on localhost
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
git clone … clickroom && cd clickroom
|
|
20
|
+
npm install
|
|
21
|
+
npm link # puts `clickroom` on your PATH
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Share any running project (one command)
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
cd ~/code/my-app
|
|
28
|
+
npm run dev # your dev server, as usual
|
|
29
|
+
clickroom # in the same project, in another terminal
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Starting clickroom + Cloudflare Tunnel…
|
|
34
|
+
✔ Sharing my-app → http://localhost:5173 (node (pid 4123) running in this project)
|
|
35
|
+
Building v1: npm run build
|
|
36
|
+
✔ Pushed v1 · First share. Viewers are told a new version is available.
|
|
37
|
+
|
|
38
|
+
Review (you, team) https://<random>.trycloudflare.com/s/my-app (press o to open it signed in)
|
|
39
|
+
Client link https://<random>.trycloudflare.com/s/my-app?t=… ← copied to clipboard
|
|
40
|
+
All sessions https://<random>.trycloudflare.com/
|
|
41
|
+
|
|
42
|
+
p push new version o open review c copy client link q quit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- **Detection:** clickroom looks for a server **started from this project** (a listening port whose process runs inside the project's git root). Failing that, it looks for a build folder (`dist/`, `build/`, `out/`). If the only thing it finds is an unrelated common port (3000, 5173, …), it asks first and never shares it silently.
|
|
46
|
+
- **Be explicit when needed:** `clickroom 5173`, `clickroom http://localhost:3000`, `clickroom ./dist` (changes are offered as a push, not published automatically).
|
|
47
|
+
- **Project name:** from `package.json` (or the folder name). Override with `--name`.
|
|
48
|
+
- **Feedback:** you and the client both review at the public URL. Starting `clickroom`, or pressing **o**, opens the review signed in as team (queue, resolve, delete) through a one-time link.
|
|
49
|
+
- **Dev servers work through the tunnel, including HMR:** root-relative module paths, Vite/Next HMR WebSockets, and their origin checks are handled. Frame-blocking headers are stripped so the app renders in the review.
|
|
50
|
+
- **Versions:** clients only see what you push. See [Versions](#versions).
|
|
51
|
+
- **Several projects:** run `clickroom` in each. The first terminal owns the server and tunnel; later ones attach to the same tunnel host. Quitting the owner (q or Ctrl+C) closes the tunnel for all of them, and attached terminals notice and exit. See every session at the *All sessions* link.
|
|
52
|
+
- **Options:**
|
|
53
|
+
- `--local`: no tunnel.
|
|
54
|
+
- `--no-open`: don't open the browser.
|
|
55
|
+
- `--allow-email '*@client.com'`: Quick Tunnel that requires email login.
|
|
56
|
+
- `--tunnel-name NAME`: named tunnel (stable hostname, works with Cloudflare Access).
|
|
57
|
+
|
|
58
|
+
Try it on the bundled demo: `npm run demo`. For a demo full of test cases (comments in every state, fixes to check, a removed element, questions from draft to done, a hidden page, an agent queue), run `npm run demo:seed`: it pushes the Fieldnote prototype as the project `demo` in two versions and fills it through the API. Run it again to reset (it saves what was there to `~/.clickroom/backups` first).
|
|
59
|
+
|
|
60
|
+
### Without the one-shot command
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
clickroom add checkout ./dist --watch # or a URL: clickroom add checkout http://localhost:4173
|
|
64
|
+
clickroom dev --tunnel # serves every registered project, prints a dashboard link
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Versions
|
|
68
|
+
|
|
69
|
+
The public link serves **pushed versions**, never your unsaved work in progress.
|
|
70
|
+
|
|
71
|
+
- The first `clickroom` in a project pushes **v1**. After that, press **p** in its terminal (or run `clickroom push "note"`, or click *Push new version* in the review or on the sessions page) to push the next one.
|
|
72
|
+
- **What a push does:** it runs the project's `build` script (npm, pnpm, yarn or bun, picked by lockfile), takes the output (`dist/`, `build/`, `out/` or `.output/public`) and stores it as a frozen snapshot. A static folder is copied as is.
|
|
73
|
+
- Override with `--build "cmd" --out dir`.
|
|
74
|
+
- Projects without static output (SSR) can use `clickroom push --live`, a label-only version that shows the live dev server.
|
|
75
|
+
- **Backends stay live:** a snapshot freezes the UI. Requests it can't answer that aren't page loads (API calls, form posts) still go to your running dev server, so apps with a backend keep working.
|
|
76
|
+
- **In the review:**
|
|
77
|
+
- The toolbar shows the version you're viewing. Older versions stay viewable from its menu.
|
|
78
|
+
- When you push, everyone watching gets a "v4 pushed: View" prompt. Their view never switches on its own.
|
|
79
|
+
- Old versions show a "View latest" banner.
|
|
80
|
+
- The team can also pick **Live dev server** to see unpushed work. Clients can't.
|
|
81
|
+
- **Comments:** every comment records the version it was left on (shown as a `v2` tag), and stays visible on every version.
|
|
82
|
+
- **Checking comments in a new version** (team): after a push, *View & check comments* (in the push prompt, the version menu or the sidebar banner) goes through every open comment, opening its page, tab and dialog the way a person would, and reports which ones are **found**, **moved** (found, but the element changed) or **lost**.
|
|
83
|
+
- Lost comments show *View it in v3*, which opens the version where the comment was last seen. Drag its pin to where it belongs in the new one.
|
|
84
|
+
- The version menu shows how many comments each version lost. Checks are stored next to a comment and never change it.
|
|
85
|
+
|
|
86
|
+
## Sessions overview
|
|
87
|
+
|
|
88
|
+
Each `clickroom` terminal is a live **session**. The dashboard (`/`, team only) shows every project as a tile:
|
|
89
|
+
- **A preview** of the latest version. The first time someone on the team opens a new version, their browser takes a picture of its first screen.
|
|
90
|
+
- **The version chip** (`v15 · 52m ago · 4 fixes`): the push note shows on hover. Clicking it opens the versions panel, with every version's changes, its fixes and comments, and *Open vN*.
|
|
91
|
+
- **New changes since v15:** the terminal compares the project folder with what the last push saw, every 30 s, file sizes and dates only, and git commits when it's a repo. The tile then offers **Push v16**: one click, and the terminal writes the version note itself and pushes.
|
|
92
|
+
- **How the note is written:** clickroom stores a git tree of the exact code at every push, in a throwaway index, so your index, branches and stash stay untouched. It diffs that against the code now and asks Claude Code on this Mac (`claude -p`, model haiku, no tools) to describe the changes the way a reviewer would notice them, together with the comments this version fixes. Only the diff goes to Claude, through your own login; lockfiles, images and build output stay out.
|
|
93
|
+
- **Fallbacks:** without git or Claude, the note lists the fixed comments, or else the changed file names. Set `CLICKROOM_AUTO_NOTE=off` to never ask Claude.
|
|
94
|
+
- **Editing:** correct a note under *Edit note* in the versions panel.
|
|
95
|
+
- **In the terminal:** pressing `p` and then Enter does the same. Without a running terminal the tile can't look; it says whether there were unpushed changes when the session ended.
|
|
96
|
+
- **Things to clarify, open comments and fixes to check.**
|
|
97
|
+
- **Public or Invite only**, then **Open prototype** and **⋯**: Share…, Copy link, Push new version, the session details, and Remove (owner).
|
|
98
|
+
|
|
99
|
+
In a review, the project name in the toolbar is a switcher with the same list. `clickroom list` prints it in the terminal.
|
|
100
|
+
|
|
101
|
+
## Workspaces
|
|
102
|
+
|
|
103
|
+
On clickroom.dev every team has a workspace at its own address, `<workspace>.clickroom.dev`, with its own people, projects, comments and builds. Each review is at `<workspace>.clickroom.dev/<project>`.
|
|
104
|
+
|
|
105
|
+
- **Signing up:** on clickroom.dev, *Start a workspace* signs you in with your email address (a link, or Google) and asks for a name and an address. You're its owner and signed in there right away.
|
|
106
|
+
- **Your workspaces:** clickroom.dev lists every workspace your address is in, as an owner, teammate or client. *Open* signs you in there. Each workspace's dashboard has *Switch* next to its name.
|
|
107
|
+
- **Sharing from a computer:** `clickroom login <workspace>`, then `clickroom` in a project. Builds are uploaded. Live dev servers (the relay) belong to the first workspace only.
|
|
108
|
+
- **Separate by design:** a sign-in, a CLI key or a share link of one workspace opens nothing in another, even for a project with the same name (`test/workspaces.test.js`). The admin key and the Slack webhook belong to the first workspace.
|
|
109
|
+
|
|
110
|
+
How it's set up:
|
|
111
|
+
- `APP_HOST` (`{ws}.clickroom.dev`) and `DEFAULT_WS` (`yna`) turn workspaces on. The first workspace keeps its storage from before workspaces (Registry `registry`, rooms and builds by project name). Others use Registry `ws:<slug>`, and rooms and builds under `<slug>/`.
|
|
112
|
+
- The `Directory` Durable Object knows every workspace and which email addresses are in each. Each workspace's Registry keeps that list up to date.
|
|
113
|
+
- The bare domain (`AUTH_URL`) has the public pages, `/login` and the list of your workspaces. It also keeps the API for CLIs set up before, and Google's return address.
|
|
114
|
+
- Prototypes run on `{name}--{ws}.clickroom-preview.dev` (`PROTO_HOST`), a separate site. Each review address frames the review from there.
|
|
115
|
+
- `PASSTHROUGH_HOSTS` lists hosts under the wildcard route that aren't the app's, such as the relay's tunnel.
|
|
116
|
+
|
|
117
|
+
## People
|
|
118
|
+
|
|
119
|
+
Everyone except share-link clients signs in as themselves, by email address. A browser stays signed in for 30 days from its last visit; signing in again while signed in (clickroom.dev's *Sign in*) goes straight to the dashboard.
|
|
120
|
+
|
|
121
|
+
- **You (the owner):** `clickroom` signs you in with a one-time link (15 minutes). It opens when the session starts and whenever you press **o**. Under *Edit* (top right of the dashboard) you can set your name and email.
|
|
122
|
+
- **Inviting someone:** on the dashboard's *People* panel, enter their email, choose *Client* or *Teammate*, and pick projects. clickroom emails the invite, valid once within 7 days; the link is also shown to copy.
|
|
123
|
+
- Teammates work as the team on their projects.
|
|
124
|
+
- Clients review exactly as with a share link (comments, answers, *Looks good*), but under their own verified name.
|
|
125
|
+
- **Signing in on another device:** at `/__ps/signin` anyone invited enters their email and gets a sign-in link. A browser stays signed in for 30 days.
|
|
126
|
+
- **Revoke** signs someone out everywhere at once, project hosts included. *Edit projects* changes what they can open.
|
|
127
|
+
- **Only invited people** on a project card turns its share link off; only people you invited get in.
|
|
128
|
+
|
|
129
|
+
### Sharing a project
|
|
130
|
+
|
|
131
|
+
**Share** on a project card opens its share panel:
|
|
132
|
+
|
|
133
|
+
- **People with access:** their Google picture, name, email and role on *this* project. Roles are per project, so someone can be a teammate on one project and a client on another, and their comments carry the matching role. Change a role with the dropdown. The **⋯** menu has *Send invite again* and *Remove from project*. The owner and teammates on all projects show as fixed; you change those under *People*.
|
|
134
|
+
- **Invite by email:** gives that address a role on this project and emails an invite that names it.
|
|
135
|
+
- **Requests:** people who opened the link without access asked to join. Pick a role and **Accept** or **Decline**. You also get an email with *Give access* and *Decline* links, which ask you to confirm first. Requests show in Slack too, if it's set up. A declined person can't ask again for 30 days. Requests are forgotten after 30 days.
|
|
136
|
+
- **General access:** *Invite only* means people sign in (Google or an email link); invited people go straight in, anyone else can request access. *Public* means anyone with the link opens it without signing in. Open links (`?t=`) you sent earlier keep working while the project is Public.
|
|
137
|
+
- **Copy link** gives the project's one link (`/join/<code>`). **Reset link** makes a new one, and the old link stops working.
|
|
138
|
+
|
|
139
|
+
Teammates see the panel read-only. The *People* panel is the overview of everyone, and *Revoke* there removes someone from every project.
|
|
140
|
+
|
|
141
|
+
The admin key (`ADMIN_TOKEN`) is for machines only: the CLI, MCP, agents and the relay. It works as a bearer header and is never accepted from a URL or a browser cookie.
|
|
142
|
+
|
|
143
|
+
### Teammates share from their own computer
|
|
144
|
+
|
|
145
|
+
Teammates share their own builds with you through your clickroom (for example clickroom.dev). They don't need your Cloudflare account, the clickroom code or the admin key.
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
npm install -g clickroom
|
|
149
|
+
clickroom login # opens clickroom in the browser: approve this computer
|
|
150
|
+
cd ~/code/their-app
|
|
151
|
+
clickroom # builds it, uploads it as v1; p pushes the next version
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- **`clickroom login`** asks clickroom for a login and opens a page where they approve this computer. They sign in from their invite there, as a teammate. Clients can't connect the command. The page and the terminal show the same code to compare.
|
|
155
|
+
- The terminal then gets a key of its own in `~/.clickroom/credentials.json` (readable only by them). It lasts 90 days from its last use.
|
|
156
|
+
- `clickroom logout` ends it. *Revoke* on the People panel ends it as well, along with their browser sign-ins.
|
|
157
|
+
- `clickroom login https://other.example` connects to another clickroom server; `clickroom whoami` shows who's connected.
|
|
158
|
+
- **Their project** belongs to them and to you. They become its teammate and it shows on your dashboard with their name. A folder of theirs never takes over a project with the same name; it gets its own (`my-app-2`).
|
|
159
|
+
- **Only pushed builds.** Their dev server stays on their computer, so there's no Live view, and API calls aren't passed on to it. Each push runs the project's build script and uploads the output. Without a build script, use `clickroom push --build "…" --out dist`.
|
|
160
|
+
- **Uploads, not deploys.**
|
|
161
|
+
- Files are stored once by content in R2, and each push only sends files that changed.
|
|
162
|
+
- Each file can be up to 25 MB, and a version up to 20,000 files.
|
|
163
|
+
- The versions panel shows who pushed each version.
|
|
164
|
+
- You get a notification when a teammate pushes a version, and a Slack message if Slack is set up.
|
|
165
|
+
- Everything else works from their terminal as from yours:
|
|
166
|
+
- *Push vN* on the dashboard;
|
|
167
|
+
- *Find gaps with Claude*;
|
|
168
|
+
- `clickroom process`;
|
|
169
|
+
- MCP.
|
|
170
|
+
|
|
171
|
+
Setup (once, cloud): turn on R2 in the Cloudflare dashboard, then run `npx wrangler r2 bucket create clickroom-builds` and `clickroom deploy`. `wrangler.cloud.jsonc` binds the bucket as `BUILDS`.
|
|
172
|
+
|
|
173
|
+
### Sign in with Google
|
|
174
|
+
|
|
175
|
+
Everyone you invited can also choose **Continue with Google** on the sign-in page: you, teammates and clients. Google only proves the email address. clickroom lets someone in only when that verified address matches an invite, and only with that invite's role and projects. The first Google account used for an address stays tied to it.
|
|
176
|
+
|
|
177
|
+
Setup:
|
|
178
|
+
|
|
179
|
+
1. In the Google Cloud Console, create a project and configure the *OAuth consent screen*: External, scopes `openid email profile`, then **Publish**. These basic scopes need no review.
|
|
180
|
+
2. Under *Credentials*, create an *OAuth client ID* (Web application). Add the redirect URIs `https://<your domain>/__ps/oauth/google/callback` and, for local use, `http://localhost:<port>/__ps/oauth/google/callback`.
|
|
181
|
+
3. Cloud: add `"GOOGLE_CLIENT_ID"` to `vars` in `wrangler.cloud.jsonc`, then run `npx wrangler secret put GOOGLE_CLIENT_SECRET -c wrangler.cloud.jsonc` and `clickroom deploy`.
|
|
182
|
+
4. Local: put `GOOGLE_CLIENT_ID=…` and `GOOGLE_CLIENT_SECRET=…` in `.dev.vars`.
|
|
183
|
+
|
|
184
|
+
### Sending email (cloud)
|
|
185
|
+
|
|
186
|
+
Invites and sign-in links go out through Cloudflare Email Service. Setup:
|
|
187
|
+
|
|
188
|
+
1. Switch to the Workers Paid plan, which is needed to send to any address.
|
|
189
|
+
2. Onboard the domain under *Email Service → Email Sending*.
|
|
190
|
+
3. Add `"send_email": [{ "name": "EMAIL" }]` and `"MAIL_FROM": "invites@<your domain>"` to `wrangler.cloud.jsonc`, then run `clickroom deploy`.
|
|
191
|
+
|
|
192
|
+
Without it, invites still work: copy the link from the dashboard. Sign-in by email needs it.
|
|
193
|
+
|
|
194
|
+
## Comments are never lost
|
|
195
|
+
|
|
196
|
+
- **Projects are tied to their folder.** Renaming `package.json` keeps the same project, link and comments. A different folder with the same name gets its own project (`my-app-2`).
|
|
197
|
+
- **Storage lives outside the repo,** in `~/.clickroom/state` (override with `CLICKROOM_HOME`), so cleaning the clickroom repo can't delete feedback. State from older versions in `.wrangler/state` is copied there on first start.
|
|
198
|
+
- **Backups:** every push and every session exit writes `~/.clickroom/backups/<name>.json` with all threads and versions.
|
|
199
|
+
- **Removing a project keeps its comments.** They come back if the project is shared again.
|
|
200
|
+
|
|
201
|
+
## Stable links on your own domain
|
|
202
|
+
|
|
203
|
+
Quick Tunnel links (`*.trycloudflare.com`) change every time the tunnel restarts. If a Quick Tunnel drops, clickroom reopens it on a new address, and every open session prints the new links. For links that never change, use a named tunnel on a domain in your Cloudflare account:
|
|
204
|
+
|
|
205
|
+
1. Add the domain to Cloudflare and point its nameservers there.
|
|
206
|
+
2. Create a tunnel (dashboard → Networking → Tunnels), then add a published application route, e.g. `share.example.com` → `http://localhost:8791`. The port must be the one clickroom runs on.
|
|
207
|
+
3. Log wrangler in once: `npx wrangler login` in the clickroom folder.
|
|
208
|
+
4. Run `clickroom dev --tunnel --tunnel-name <tunnel>` (or `clickroom --tunnel-name <tunnel>` in a project). The name is remembered, so later runs use it automatically; `--tunnel-name none` goes back to Quick Tunnels.
|
|
209
|
+
|
|
210
|
+
## Run in the background (macOS)
|
|
211
|
+
|
|
212
|
+
`clickroom service install` runs `clickroom dev --tunnel` as a launchd agent. It starts at login, restarts if it crashes (and clears a leftover server from a hard crash), and needs no open terminal. `clickroom service status | logs | uninstall` manage it. Project terminals (`clickroom` in a project) are then only needed for pushing versions and showing the session as live.
|
|
213
|
+
|
|
214
|
+
## Always up: host on Cloudflare
|
|
215
|
+
|
|
216
|
+
With the local server alone, links only answer while your Mac is awake. `clickroom cloud setup` deploys the same worker to your Cloudflare account (`wrangler.cloud.jsonc`):
|
|
217
|
+
|
|
218
|
+
- **In the cloud:** comments, versions, the review UI, the dashboard, and every pushed snapshot. With the R2 bucket bound (`BUILDS`), each `clickroom push` builds, uploads only the changed files, then announces the new version. Without it, the snapshot ships as static assets: the push deploys the worker first. Versions pushed that way keep working after you switch.
|
|
219
|
+
- **On your Mac:** the Live view and app API calls. The cloud relays them through `RELAY_URL`, a tunnel to the local server (keep the background service running). When the Mac is off, those parts show "offline" and everything pushed keeps working.
|
|
220
|
+
- Setup copies every local project with its share token and full history, so existing links keep working. The CLI, sessions and MCP then talk to the cloud. `clickroom cloud url <url>` switches to a custom domain; `clickroom cloud off` goes back to local only.
|
|
221
|
+
- Point your public hostname at the worker (custom domain `share.example.com`) and the tunnel at a second hostname for the relay (`local.example.com` → `http://localhost:8791`).
|
|
222
|
+
- Without R2, the Free plan allows 20,000 static files per deploy, and pushes warn when you get close. Delete old `site/p/<name>/v*` folders to make room. Uploaded versions don't count.
|
|
223
|
+
|
|
224
|
+
## Reviewing
|
|
225
|
+
|
|
226
|
+
**Pages.** The project menu (the project name in the toolbar) lists every page of the version on screen, for everyone, so nobody gets stuck on a page without links. It combines the build's HTML files (sent by `clickroom push`), pages someone viewed in that version (how SPAs and live dev servers get theirs), and pages with comments. Each page shows a comment badge with its open comments (the ones the viewer can see, live). With more than 8 pages, type to filter and press Enter to open the first match. The gear sets which pages clients see. A page switched off is team-only: clients don't see it in the list, and opening it anyway (a link, a typed URL, an app route) shows "This page isn't shared". Comments on a hidden page are hidden from clients too, live; questions still reach them. Every page is shared until the team switches it off.
|
|
227
|
+
|
|
228
|
+
**Tabs inside one page.** Prototypes that switch tabs or forms without changing the URL are handled too. Each comment remembers the selected tab (whatever the app marks with `aria-current`, `role="tab" aria-selected`, or `data-state="active"`; otherwise a tab marked only by a class like `.active`, in something named like tabs or navigation), shows only on that tab, and clicking it in the list switches the prototype to it. For older comments, the team gets **Sort into tabs**, which assigns every comment found on exactly one tab, plus **Belongs to …** per comment; drag a pin to move it.
|
|
229
|
+
|
|
230
|
+
**Dialogs, menus and popovers.** A comment made inside one remembers it and the clicks that opened it (e.g. *Actions → Share…*). It shows only while that overlay is open, and selecting it opens the overlay again. While commenting, overlays stay open: presses, focus and Escape go to clickroom, not the prototype. Pins behind an open dialog are hidden.
|
|
231
|
+
|
|
232
|
+
**Naming views yourself.** Add `data-clickroom-view="Billing"` to any element that's visible on a view, or `data-clickroom-layer="Share dialog"` to an overlay's root, when a prototype has no ARIA markers clickroom can use.
|
|
233
|
+
|
|
234
|
+
**Comments open on screen.** Clicking a pin, or a comment in the panel, opens that conversation in a card next to its pin, like in Figma, conversation first: a quiet header (number, status, the agent robot, ✓ resolve, ⋯ for Copy link and Delete), each message once, every status step as a small dot line where it happened (Queued for the agent, Fixed in v3, Reopened, Looks good · resolved; the same words for team and client), a bar above the reply box that says where it stands and holds the next step in plain words with a yes / no where it asks something (*With the agent.* · Take back, *Fixed in v3. Is it right now?* · Yes / Not yet, *Reopened.* · Queue again, *This part changed in v3. Still needed?* · No, resolve / Yes), and a reply box that sends itself. **Pins are draggable** (team): drag one onto the element it's about and the comment moves there, with a new screenshot; Esc while dragging puts it back. The panel stays the overview; Esc or a click on the prototype closes the card. **A new version is checked in place.** A comment made on an earlier version whose element isn't there as it was isn't "lost": a dashed ghost pin marks where it was made, its row says *Changed in v3*, and opening it asks "Still needed?": **No, resolve**, or **Yes** (the team places it again in this version). For a fix whose element is gone it asks "Removed in v3. Is that what you asked for?" (**Yes** / **No**). Moved elements are followed without a word. **Resolving** (✓, or Yes on a fix) takes the comment off the prototype and closes its card; the toast's **Reopen** undoes it, and the panel's Resolved tab opens it again (its pin shows while you look at it) with **Reopen** on the card. A comment without a spot to show it at (its element is gone, its tab or dialog can't be opened) opens in the panel instead.
|
|
235
|
+
|
|
236
|
+
**Screenshots.** Each new comment saves a picture of the spot as the reviewer sees it: cropped around the element, with the pin or region drawn in, including open dialogs and anything typed. It's taken in the reviewer's browser, shown in the comment, and passed to agents. Reviewers can untick *Screenshot* per comment.
|
|
237
|
+
|
|
238
|
+
- Everything lives in one floating toolbar (drag it by its handle; double-click to reset): the logo (back to the dashboard), the project name (its pages), version, browse/comment, viewport, people online, you, Agent (team) and **Activity**, with a red number when something needs you (fixes to check, things to clarify).
|
|
239
|
+
- **Activity** is one panel for everything on the prototype, with a tab bar at the bottom that lines up with the toolbar: **Comments** (two tabs, Open and Resolved; Open in sections: To check, This page, Other pages; each comment a card like Clarify's: its pin number with the faces of who's in it, Queue for the agent and Resolve on hover, who and when, the comment, replies and the page), **Clarify** (below) and **Tasks** (team: bigger work that isn't about one spot, like "make a page about pricing"; Open and Done, each task a card; the robot sends one to the agent queue, ✓ marks it done). Queueing a comment closes it; with **Hide queued comments** (Agent ▾, on by default, per browser; an experiment) queued comments leave the list for one "3 with the agent ›" row that opens the Agent queue, while their pins stay with the robot. Each tab shows its red number; ✕ closes the panel, and it can be dragged.
|
|
240
|
+
- **Browse** mode (arrow) uses the prototype normally. Press **C** (or click the comment icon) to annotate:
|
|
241
|
+
- **click** to drop a pin on an element
|
|
242
|
+
- **drag** to mark a region
|
|
243
|
+
- Each annotation is anchored to the element (selector + relative offset), so pins follow layout changes. A pin falls back to its original coordinates, marked "element moved", if the element disappears.
|
|
244
|
+
- Live presence avatars (click one to jump to that person's page), cursors, and new comments arrive instantly.
|
|
245
|
+
- Viewport presets: desktop, tablet (834px) and mobile (390px). Navigation inside the prototype updates the URL hash, so links deep-link to a page or thread (`#/about.html?thread=3`).
|
|
246
|
+
- **Roles:**
|
|
247
|
+
- **Clients** (share link) can comment, reply, resolve and reopen.
|
|
248
|
+
- **Team** (admin) can also *Send to agent*, *Queue open for agent*, delete, and copy the hand-off.
|
|
249
|
+
- **Agents** post through MCP or the CLI and are labeled 🤖.
|
|
250
|
+
|
|
251
|
+
**Only what the team queues goes to the agent.** Queue a comment while you write it with **Post & queue** (⇧⌘↵), or afterwards with the status badge on the open comment, which is the toggle (a copy of the card flies into the toolbar's Agent button, so you see where it went) (**Queue for agent** → **Queued for agent**, click again to take it back) (the robot on its row in the list, or on an answered question's card). The **▾** next to the toolbar's Agent button holds **Queue new comments**, on by default: **Post & queue** is the main button (⌘↵) and **Post only** the other one; turned off, it's the other way round. The comment box has a ✕ to close it and a **Screenshot** switch, remembered per browser. Queued pins carry a robot badge, and while anything is queued the Agent button glows (a colorful beam on its edge with a glow pulsing around it, after libraries.dev/beam; still with reduced motion); the toolbar's **Agent** button (or **A**) opens the agent queue: everything that goes into the next version, with ✕ to take an item back, **Add a task** (free text for the agent that isn't about one spot, such as "make the footer dark": team only; added here it's queued right away, added in Activity's Tasks tab it waits there until you send it; listed under Tasks in the brief; ✕ takes it out of the queue, and it stays in Tasks) and **Copy to Agent**, which puts a brief on the clipboard (what to do, the project folder, the queued items and nothing else) to paste into Claude Code. `clickroom process` and the MCP feedback tools use the same rule: nothing queued, nothing to do.
|
|
252
|
+
|
|
253
|
+
Thread status flow: `open → queued` (team sends it to the agent) `→ review` (the agent marked it fixed) `→ resolved`. There is no "agent working" state: you watch the agent in your terminal, and a state that isn't live would only get stuck. Every status change is recorded in the thread.
|
|
254
|
+
|
|
255
|
+
**Checking fixes.** A fix waits in *review* for the next pushed version. When that version arrives, everyone watching gets "#4 is fixed in v5: check it", and the comment asks *Fixed in v5. Is it right now?* with **Yes** (resolves it) and **Not yet** (reopens it and asks what still needs to change). Fixes to check are listed under *To check*.
|
|
256
|
+
|
|
257
|
+
**Clarify (questions for the client).** Most feedback belongs on the prototype as comments; the sidebar is for those. **Clarify** is for what has no place in it and what an agent can't know: content, business rules, the why and for whom behind a request, and assumptions to check ("Annual plans are paid upfront."). Most are drafted by an agent to cover gaps. They live in Activity's **Clarify** tab (or **Q**), in tabs: **Drafts** (the team's), **Open** and **Resolved**. (Agents and the API still call them questions: `clickroom_ask`, `kind: question`.) Drafts holds what an agent drafted, which only the team sees (**Send all**); Open every other open question. Each is a card: its **#number** next to the faces of who's in it, who asked and when, the question, and how many replies. A small dot after when means **unread**: someone else wrote since you last opened it (remembered per browser); the red number on Clarify counts unread ones for the team, and what waits on them for a client. Hovering a card shows its actions: **Send to agent** and **Resolve** (✓), or for a draft **Ask the client** and **Remove**. Resolved has the same cards, each saying where it ended up (*Fixed in v5*, or *Resolved*: done without the agent). A card opens the question as a conversation, like an open comment: ‹ Clarify goes back (Esc), ‹ › step through the tab (← →), and the title shows its #number to refer to. The team clicks the question to edit it (and its **Now:** line), resolves it with ✓ top right (a draft has its bin there), and uses the main button: **Ask the client** on a draft, **Send to agent** once there's an answer: the client's, or the team's own (a reply, or what's typed in the reply box, which goes along). The reply box follows up with the client; on a draft it answers it yourself, so the client is never asked and it goes straight to an agent. Each action moves on to the next question that needs you. Only questions sent to an agent are handed off (`clickroom process`, `status=answered`); they leave the team's list for the Agent queue (**Take back** from there). Clients answer every question in their own words in the reply box, and each answer moves on to the next; they get a "3 things to clarify · Start" nudge above the toolbar (the team gets "2 drafts to clarify · Review"). **Find gaps with Claude** (✨ next to +, and the empty Clarify tab) has Claude look for what it can't know: your clickroom terminal for the project runs Claude Code in the background in the project folder, read-only (it may only read files and ask through clickroom's MCP server), and each gap it finds lands in Drafts as it goes ("Claude is reading the prototype…", then "✓ 11 drafts from Claude"). Without a running terminal, **Copy the prompt for Claude Code** gives the same instructions to paste into Claude Code. Agents add questions and assumptions through MCP (`clickroom_ask`) or the CLI as drafts, with a **Now:** line saying what the prototype does until it's answered. `clickroom questions import <name> items.json` loads a list (`[{key, type, text, now, page, selector}]`) as the agent, as drafts; the same `key` updates instead of adding (and keeps whether it was sent). A text starting with a short label and a colon ("Shop: Do prices include VAT?") shows the label in bold. When an agent works an answer into the prototype it marks the item *Processed*, and the next push adds the version it landed in. Questions aren't counted as feedback and stay out of the feedback digest. Clients can't add, resolve or delete questions.
|
|
258
|
+
|
|
259
|
+
## Agent hand-off
|
|
260
|
+
|
|
261
|
+
### Process everything new (one command)
|
|
262
|
+
|
|
263
|
+
```sh
|
|
264
|
+
clickroom process # inside the project folder (or: clickroom process <name>)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Gathers what the team queued for the agent: queued comments and queued answers to questions (the client answered or confirmed them). It writes them as one brief to `.clickroom/process.md` (the folder ignores itself in git) and starts Claude Code in the project on it, so you watch and approve the changes. The agent marks each comment fixed and each answer processed with a note, then runs `clickroom push`, which records the version on every marked item. `--print` writes the brief to stdout for any other agent; `--no-comments` / `--no-questions` narrow it. With nothing new it says so and stops.
|
|
268
|
+
|
|
269
|
+
### MCP (recommended)
|
|
270
|
+
|
|
271
|
+
Register once:
|
|
272
|
+
|
|
273
|
+
```sh
|
|
274
|
+
claude mcp add clickroom --scope user -- node /absolute/path/to/clickroom/bin/clickroom.js mcp
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
(Inside this repo, `.mcp.json` already registers it.) Then ask your agent something like *"Work through the queued clickroom feedback for checkout."*
|
|
278
|
+
|
|
279
|
+
| Tool | What it does |
|
|
280
|
+
| --- | --- |
|
|
281
|
+
| `clickroom_list_projects` | Projects with counts per status |
|
|
282
|
+
| `clickroom_get_feedback` | Markdown or JSON digest. `status="actionable"` (default) means the threads the team queued, nothing else |
|
|
283
|
+
| `clickroom_get_thread` | One thread with anchor and conversation, plus its screenshot as an image |
|
|
284
|
+
| `clickroom_claim` | Optional: post a note before working on a thread (the status stays as it is) |
|
|
285
|
+
| `clickroom_reply` | Ask the client a question in the thread |
|
|
286
|
+
| `clickroom_mark_fixed` | Mark a thread fixed, or an answered question processed, with a note on what changed: the next pushed version records where it landed |
|
|
287
|
+
| `clickroom_resolve` | Same as `clickroom_mark_fixed`; `close: true` resolves outright (when no check is needed, e.g. closing a settled question) |
|
|
288
|
+
| `clickroom_ask` | Ask the client a question or list an assumption, with an optional "Now", the spot it's about (`page`, `selector`, `screenshot`) and a stable `key` |
|
|
289
|
+
| `clickroom_get_questions` | Questions with their answers and whether each is waiting for the client or answered |
|
|
290
|
+
|
|
291
|
+
The MCP server talks to the running `clickroom dev` on `localhost`, so every agent action is broadcast live to reviewers.
|
|
292
|
+
|
|
293
|
+
### CLI / files
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
clickroom feedback checkout # actionable digest to stdout
|
|
297
|
+
clickroom feedback checkout --out FEEDBACK.md # …or to a file in your repo
|
|
298
|
+
clickroom feedback checkout --json --status all
|
|
299
|
+
clickroom resolve checkout 3 --note "Increased CTA to 18px" # marks it fixed; --close resolves outright
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Each item in the digest includes the page, where it is in the app (tab, dialog and the clicks that open it), the element selector, its text, a truncated HTML snippet from the built page, a screenshot link (needs the team token), which versions it was found in, the viewport, region size, and the full conversation with author roles.
|
|
303
|
+
|
|
304
|
+
## Commands
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
clickroom login [url] | logout | whoami
|
|
308
|
+
clickroom [port|url|dir] [--name NAME] [--local] [--no-open] [--allow-email ADDR] [--tunnel-name NAME]
|
|
309
|
+
clickroom push [name] [note] [--build CMD] [--out DIR] [--live]
|
|
310
|
+
clickroom dev [--tunnel] [--tunnel-name NAME] [--allow-email ADDR] [--port 8787] [--lan] [--verbose]
|
|
311
|
+
clickroom add <name> <dir|url> [--watch]
|
|
312
|
+
clickroom sync <name> | list | links <name> | remove <name>
|
|
313
|
+
clickroom feedback <name> [--status …] [--json] [--out FILE]
|
|
314
|
+
clickroom process [name] [--print] [--no-comments] [--no-questions]
|
|
315
|
+
clickroom resolve <name> <n> [--note TEXT] [--close]
|
|
316
|
+
clickroom mcp
|
|
317
|
+
clickroom service install | uninstall | status | logs
|
|
318
|
+
clickroom cloud setup | url <url> | off
|
|
319
|
+
clickroom deploy
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Environment variables:
|
|
323
|
+
- `CLICKROOM_HOME` (default `~/.clickroom`): comments, versions, backups, and a teammate's login and projects.
|
|
324
|
+
- `CLICKROOM_URL`: the cloud URL after `clickroom cloud setup` or `clickroom login`, else `http://localhost:8787`.
|
|
325
|
+
- `CLICKROOM_ADMIN_TOKEN` (default: read from `.dev.vars`).
|
|
326
|
+
- `CLICKROOM_TOKEN`: a teammate's key, which overrides the admin key.
|
|
327
|
+
- `CLICKROOM_AGENT_NAME` (default `Claude`).
|
|
328
|
+
|
|
329
|
+
Slack: set `SLACK_WEBHOOK_URL` to a Slack [Incoming Webhook](https://api.slack.com/messaging/webhooks) and clickroom posts when someone comments, replies or resolves a thread (agents' activity is left out). Put it in `.dev.vars` locally, and for the cloud run `wrangler secret put SLACK_WEBHOOK_URL -c wrangler.cloud.jsonc`. The link in each message leaves out the client token, so the team opens it with their admin login.
|
|
330
|
+
|
|
331
|
+
## Security notes
|
|
332
|
+
|
|
333
|
+
- A Quick Tunnel URL is public. Every prototype, API call and WebSocket needs the project's share token (`?t=` once, then an HttpOnly cookie, `Secure` over the tunnel).
|
|
334
|
+
- The admin key (`ADMIN_TOKEN`) is generated into `.dev.vars` on first run. It is accepted only as a bearer header (CLI, MCP, agents). People sign in by email or with one-time links instead (see [People](#people)). *Rotate link* on the dashboard invalidates a leaked client link.
|
|
335
|
+
- For stricter control, use `--allow-email` on a Quick Tunnel, or a named tunnel behind Cloudflare Access.
|
|
336
|
+
- Share built output where possible. A proxied `vite dev` server can expose source files through HMR.
|
|
337
|
+
- Screenshots store what was on the reviewer's screen, including anything typed into forms. Reviewers can untick *Screenshot* on a comment when the spot shows real personal data.
|
|
338
|
+
|
|
339
|
+
### Isolated prototypes (one origin per project)
|
|
340
|
+
|
|
341
|
+
Set `PROTO_HOST` (a hostname template such as `{name}-p.clickroom.dev`) and `APP_URL` (the main origin, e.g. `https://clickroom.dev`) and every project gets its own origin. That origin serves the project's review page, its prototype, and that project's part of the API. Prototype code then can't reach the admin API, the dashboard or other projects:
|
|
342
|
+
|
|
343
|
+
- The admin token is never accepted on a project host. The team signs in there with a cookie that only covers that project. The dashboard's *Open prototype* (`/__ps/go/<name>`) hands over a 60-second sign-in code by POST.
|
|
344
|
+
- Old links on the main origin (`/s/<name>?t=…`) redirect to the project host.
|
|
345
|
+
- API writes and WebSockets from other origins are refused. Over https, cookies use the `__Host-` prefix.
|
|
346
|
+
|
|
347
|
+
`clickroom --local` turns this on by itself (`<name>.localhost`). A tunnel has a single hostname, so there prototypes still share clickroom's origin. To turn it on for the cloud deployment:
|
|
348
|
+
|
|
349
|
+
1. In the clickroom.dev DNS, add a proxied wildcard record: `*` AAAA `100::`. The existing `share` and `local` records keep precedence over it.
|
|
350
|
+
2. In `wrangler.cloud.jsonc`, add `{ "pattern": "*-p.clickroom.dev/*", "zone_name": "clickroom.dev" }` to `routes`, and `"PROTO_HOST": "{name}-p.clickroom.dev", "APP_URL": "https://clickroom.dev"` to `vars`. Then run `clickroom deploy`.
|
|
351
|
+
|
|
352
|
+
**Moving the app to another address.** Put the old hostnames in `OLD_HOSTS` (comma-separated) and keep their routes. Everything there goes to the same path at `APP_URL` (API calls keep their method), a page opened while signed in brings the sign-in along, and open tabs are asked to reload. Add the new `…/__ps/oauth/google/callback` to the Google OAuth client first.
|
|
353
|
+
|
|
354
|
+
## Development
|
|
355
|
+
|
|
356
|
+
```sh
|
|
357
|
+
npm test # boots wrangler on a random port with isolated state and runs the integration suite
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Code layout:
|
|
361
|
+
|
|
362
|
+
- `worker/`: `index.js` (router and auth), `room.js` (ProjectRoom Durable Object), `registry.js`, `handoff.js`
|
|
363
|
+
- `site/__ps/`: review shell and dashboard
|
|
364
|
+
- `cli/`: CLI (`share.js` session command, `push.js` versions, `detect.js` dev-server detection, `dev.js` wrangler runner, `mcp.js`)
|
|
365
|
+
- `examples/demo/`: a sample prototype
|
|
366
|
+
- `examples/screens/`: clickroom's own screens as a static prototype, so clickroom can be reviewed in clickroom. `npm run screens` boots a throwaway worker, fills a made-up project (Fieldnote) with comments, questions and versions in every state through the real API, and writes `examples/screens/dist`: the real dashboard and review pages, with `mock.js` answering their API calls and WebSocket from that capture. Edits on the pages last until a reload. It's registered as the project `clickroom` with that build command, so `clickroom push clickroom` rebuilds it from the current UI and pushes it. The build stops if `share.js` or `dashboard.js` no longer contain a line it rewrites; update `build.js` when that happens.
|