Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c1241ee
feat(collab): add packages/shared/collab protocol contract (Slice 1)
backnotprop Apr 18, 2026
91d732d
feat(collab): add apps/room-service Worker + Durable Object skeleton …
backnotprop Apr 18, 2026
b673ec4
feat(collab): durable room engine — event sequencing, admin, lifecycl…
backnotprop Apr 18, 2026
3166f08
feat(collab): browser/direct-agent client runtime + React hook (Slice 4)
backnotprop Apr 19, 2026
f68c8cd
WIP: pre-consolidation snapshot (anchor for Live Rooms V1 cleanup)
backnotprop Apr 19, 2026
a6380a2
refactor(collab): move 5 collab hooks to packages/ui/hooks/collab/
backnotprop Apr 19, 2026
78481d7
refactor(editor): extract useStartLiveRoom from App.tsx (Phase 1)
backnotprop Apr 19, 2026
4e000a5
refactor(editor): move checkbox pending-state derivation next to useC…
backnotprop Apr 19, 2026
62e97e8
chore(collab): clean stale comments + dedup fake-presence palette (Ph…
backnotprop Apr 19, 2026
68ab47a
refactor(collab): consolidate admin error-code contract (Phase 4)
backnotprop Apr 19, 2026
2a0bca9
fix(collab): hoist ThemeProvider + align room dialogs with canonical …
backnotprop Apr 19, 2026
236be66
feat(collab-agent): package skeleton + dep graph verification (Phase 1)
backnotprop Apr 19, 2026
97006d8
feat(collab-agent): pure agentIdentity module + admin URL guard + hea…
backnotprop Apr 19, 2026
c4920ec
feat(collab-agent): join + read-plan + read-annotations + read-presen…
backnotprop Apr 19, 2026
0c11551
feat(collab): visual marker for agent cursors + avatars (Phase 4)
backnotprop Apr 19, 2026
a3fc8f0
feat(collab-agent): comment subcommand — block-level COMMENT posting …
backnotprop Apr 19, 2026
e21470f
feat(collab-agent): demo subcommand — walk headings with block-space …
backnotprop Apr 19, 2026
a2a2e10
docs(collab-agent): AGENT_INSTRUCTIONS.md + README.md (Phase 7)
backnotprop Apr 19, 2026
cb8b272
test(ui): selection-accuracy matrix + follow-up spec note (Phase 8)
backnotprop Apr 19, 2026
911046b
fix(collab-agent): demo confirms per-heading echoes + cursor x/y rand…
backnotprop Apr 19, 2026
c37055a
feat(collab): Room menu → Copy agent instructions (shell-safe payload)
backnotprop Apr 19, 2026
2827d35
chore: add wrangler to root devDeps
backnotprop Apr 19, 2026
aedde9e
feat(collab): agent instructions nudge default behavior (demo-first)
backnotprop Apr 19, 2026
c421b59
chore: stop tracking internal specs/ docs
backnotprop Apr 22, 2026
968e0cd
fix(collab): route participant.left through message queue
backnotprop Apr 22, 2026
69f242d
chore(room-service): remove fake-presence demo script
backnotprop Apr 22, 2026
ef88358
docs: drop process metadata from code comments
backnotprop Apr 22, 2026
daf0c48
refactor(collab): delete unused AdminControls component
backnotprop Apr 22, 2026
6a5a89c
feat(collab): remove lock/unlock admin commands
backnotprop Apr 22, 2026
9359cc3
docs: scrub lock/unlock references from comments
backnotprop Apr 22, 2026
6cb8023
docs(collab): complete lock/unlock residual sweep
backnotprop Apr 22, 2026
46c1641
docs(collab): third-pass lock/unlock comment cleanup
backnotprop Apr 22, 2026
1bc4988
refactor(collab): remove dormant readOnly prop
backnotprop Apr 22, 2026
7b83dbf
refactor(collab): remove lock-era dead code from admin path
backnotprop Apr 22, 2026
058b01b
feat(collab): 30-day auto-expiry via DO alarm, collapse terminal UX
backnotprop Apr 23, 2026
0190ad5
fix(collab): update straggler roomStatus references
backnotprop Apr 23, 2026
b909ca8
chore(collab): drop imports orphaned by the terminal-UX collapse
backnotprop Apr 23, 2026
ec00de8
chore(collab): sweep final terminal-state remnants + close purge TOCTOU
backnotprop Apr 23, 2026
094f6a4
chore(tests): trim duplicated + trivial test cases (-143 LOC)
backnotprop Apr 23, 2026
6828c4d
fix(collab): fresh joins to gone rooms route to RoomUnavailableScreen
backnotprop Apr 23, 2026
6d5f457
Merge origin/main into feat/collab
backnotprop Apr 23, 2026
b9e1c10
fix(collab-agent): preserve literal string "true" as a flag value
backnotprop Apr 23, 2026
c6ebe7e
ci(room-service): add CD pipeline + custom domain route
backnotprop Apr 24, 2026
fff99cf
chore: remove docs/adversarial_rubric.md (moved to .agents/skills/rel…
backnotprop Apr 25, 2026
58158cd
chore: track adversarial rubric in release skill references
backnotprop Apr 25, 2026
7c010fe
feat(room-service): landing page — upload a document, create a room
backnotprop May 12, 2026
b8f15ee
feat(room-service): never-expiry option, cross-origin cookies, collab…
backnotprop May 12, 2026
27e2437
docs(marketing): add shared rooms architecture page with SVG diagram
backnotprop May 12, 2026
24262a8
Merge origin/main into feat/collab
backnotprop May 12, 2026
810a78c
chore: remove dev artifacts, annotate unused props
backnotprop May 12, 2026
2b5f982
docs: update privacy policy for rooms, fix landing page footer
backnotprop May 12, 2026
53d180b
Merge origin/main into feat/collab (latest)
backnotprop May 12, 2026
d80ef48
chore: restore collab docs in AGENTS.md, remove dead AppHeader import
backnotprop May 12, 2026
07b5253
refactor(editor): unify header into single AppHeader component
backnotprop May 12, 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
40 changes: 40 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'apps/marketing/**'
- 'apps/portal/**'
- 'apps/paste-service/**'
- 'apps/room-service/**'
- 'packages/**'
workflow_dispatch:
inputs:
Expand All @@ -21,6 +22,7 @@ on:
- marketing
- portal
- paste
- room

permissions:
contents: read
Expand All @@ -32,6 +34,7 @@ jobs:
marketing: ${{ steps.changes.outputs.marketing }}
portal: ${{ steps.changes.outputs.portal }}
paste: ${{ steps.changes.outputs.paste }}
room: ${{ steps.changes.outputs.room }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

Expand All @@ -54,13 +57,19 @@ jobs:
else
echo "paste=false" >> $GITHUB_OUTPUT
fi
if [[ "${{ inputs.target }}" == "all" || "${{ inputs.target }}" == "room" ]]; then
echo "room=true" >> $GITHUB_OUTPUT
else
echo "room=false" >> $GITHUB_OUTPUT
fi
else
# For push events, check what changed
git fetch origin ${{ github.event.before }} --depth=1 2>/dev/null || true

MARKETING_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^(apps/marketing/|packages/)' || true)
PORTAL_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^(apps/portal/|packages/)' || true)
PASTE_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^apps/paste-service/' || true)
ROOM_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} 2>/dev/null | grep -E '^(apps/room-service/|packages/shared/collab/|packages/editor/|packages/ui/)' || true)

if [[ -n "$MARKETING_CHANGED" ]]; then
echo "marketing=true" >> $GITHUB_OUTPUT
Expand All @@ -79,6 +88,12 @@ jobs:
else
echo "paste=false" >> $GITHUB_OUTPUT
fi

if [[ -n "$ROOM_CHANGED" ]]; then
echo "room=true" >> $GITHUB_OUTPUT
else
echo "room=false" >> $GITHUB_OUTPUT
fi
fi

deploy-marketing:
Expand Down Expand Up @@ -178,3 +193,28 @@ jobs:
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

deploy-room:
needs: detect-changes
if: needs.detect-changes.outputs.room == 'true'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest

- name: Install dependencies
run: bun install

- name: Build browser shell
run: bun run --cwd apps/room-service build:shell

- name: Deploy to Cloudflare
working-directory: apps/room-service
run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ jobs:
run: bun run typecheck

- name: Run tests
run: bun test
# See .github/workflows/test.yml for why this is `bun run test`
# and not raw `bun test`.
run: bun run test

build:
needs: test
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ jobs:
run: bun run typecheck

- name: Run tests
run: bun test
# Use the root `test` script (splits non-UI + UI-cwd) so the
# packages/ui/bunfig.toml happy-dom preload is loaded. Raw
# `bun test` from the repo root doesn't pick up that package-
# scoped preload, so UI hook tests would hit "document is not
# defined".
run: bun run test

install-cmd-windows:
# End-to-end integration test for scripts/install.cmd on real cmd.exe.
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ plannotator-local
# Local research/reference docs (not for repo)
/reference/
*.bun-build

.wrangler/
apps/room-service/public/
.claude/scheduled_tasks.lock
specs/
48 changes: 43 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ plannotator/
│ │ ├── index.html
│ │ ├── index.tsx
│ │ └── vite.config.ts
│ ├── room-service/ # Live collaboration rooms (Cloudflare Worker + Durable Object)
│ │ ├── core/ # Handler, DO class, validation, CORS, log, types, csp
│ │ ├── targets/cloudflare.ts # Worker entry + DO re-export
│ │ ├── entry.tsx # Browser shell entry — path switch: / → LandingPage, /c/:roomId → AppRoot
│ │ ├── index.html # Vite template; produces hashed chunks under /assets/
│ │ ├── vite.config.ts # Browser shell build (bun run build:shell)
│ │ ├── tsconfig.browser.json # DOM-lib tsconfig for the shell
│ │ ├── static/ # Root-level static assets copied into public/ by build:shell (favicon.svg)
│ │ ├── scripts/smoke.ts # Integration test against wrangler dev
│ │ └── wrangler.toml # SQLite-backed DO binding + ASSETS binding (run_worker_first, html_handling=none)
│ ├── vscode-extension/ # VS Code extension — opens plans in editor tabs
│ │ ├── bin/ # Router scripts (open-in-vscode, xdg-open)
│ │ ├── src/ # extension.ts, cookie-proxy.ts, ipc-server.ts, panel-manager.ts, editor-annotations.ts, vscode-theme.ts
Expand Down Expand Up @@ -58,24 +68,37 @@ plannotator/
│ │ ├── components/ # Viewer, Toolbar, Settings, etc.
│ │ │ ├── icons/ # Shared SVG icon components (themeIcons, etc.)
│ │ │ ├── plan-diff/ # PlanDiffBadge, PlanDiffViewer, clean/raw diff views
│ │ │ └── sidebar/ # SidebarContainer, SidebarTabs, VersionBrowser, ArchiveBrowser
│ │ │ ├── sidebar/ # SidebarContainer, SidebarTabs, VersionBrowser, ArchiveBrowser
│ │ │ └── collab/ # RoomStatusBadge, ParticipantAvatars, RoomHeaderControls, RoomMenu, RoomUnavailableScreen, JoinRoomGate, StartRoomModal, RemoteCursorLayer, ImageStripNotice, LandingPage, LandingPreview
│ │ ├── shortcuts/ # Keyboard shortcut registry (see Keyboard Shortcuts section below)
│ │ │ ├── core.ts # Engine: parser, formatter, dispatcher, validator
│ │ │ ├── runtime.ts # Engine: useShortcutScope, useDoubleTapShortcuts hooks
│ │ │ ├── index.ts # Barrel — re-exports engine + scopes from both subfolders
│ │ │ ├── plan-review/ # Scopes for plan-editor surfaces (annotationToolbar, annotationPanel, commentPopover, imageAnnotator, inputMethod, viewer)
│ │ │ └── code-review/ # Scopes for review-editor surfaces (ai, allFilesDiff, annotationToolbar, fileTree, prComments, suggestionModal, tourDialog)
│ │ ├── shortcuts.test.ts # Registry unit tests (parser, dispatcher, validator)
│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts, planDiffEngine.ts, planAgentInstructions.ts
│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts, planDiffEngine.ts, planAgentInstructions.ts, adminSecretStorage.ts, blockTargeting.ts
│ │ ├── hooks/ # useAnnotationHighlighter.ts, useSharing.ts, usePlanDiff.ts, useSidebar.ts, useLinkedDoc.ts, useAnnotationDraft.ts, useCodeAnnotationDraft.ts, useArchive.ts
│ │ │ └── collab/ # useCollabRoom.ts, useCollabRoomSession.ts, useLandingCreateRoom.ts, usePresenceThrottle.ts, useRoomMode.ts, useRoomAdminActions.ts, useStartLiveRoom.ts
│ │ └── types.ts
│ ├── ai/ # Provider-agnostic AI backbone (providers, sessions, endpoints)
│ ├── shared/ # Shared types, utilities, and cross-runtime logic
│ │ ├── storage.ts # Plan saving, version history, archive listing (node:fs only)
│ │ ├── draft.ts # Annotation draft persistence (node:fs only)
│ │ └── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName)
│ ├── editor/ # Plan review app
│ │ ├── App.tsx # Main plan review app
│ │ ├── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName)
│ │ └── collab/ # Live Rooms protocol, crypto, validators, client runtime, React hook
│ │ ├── types.ts # Protocol types + runtime validators
│ │ ├── crypto.ts # HKDF key derivation, HMAC proofs, AES-GCM payload encrypt/decrypt
│ │ ├── ids.ts # roomId/secret/opId/clientId generators
│ │ ├── url.ts # parseRoomUrl / buildRoomJoinUrl / buildAdminRoomUrl (client-only)
│ │ ├── constants.ts # ROOM_SECRET_LENGTH_BYTES, ADMIN_SECRET_LENGTH_BYTES, WS_CLOSE_*
│ │ ├── strip-images.ts # toRoomAnnotation, stripRoomAnnotationImages
│ │ ├── redact-url.ts # redactRoomSecrets (scrub #key=/#admin= from telemetry/logs)
│ │ └── client-runtime/ # CollabRoomClient class, createRoom, joinRoom, apply-event reducer
│ ├── editor/ # Plan review app (App.tsx) + room-mode shell
│ │ ├── App.tsx # Plan review editor (local + room-mode prop)
│ │ ├── AppRoot.tsx # Mode fork (local | room | invalid-room); package default export
│ │ └── RoomApp.tsx # Room-mode shell — identity gate, session, overlays, delete/expired fallbacks
│ │ └── shortcuts.ts # planReviewSurface + annotateSurface — composes plan-review scopes into per-surface registries
│ └── review-editor/ # Code review UI
│ ├── App.tsx # Main review app
Expand Down Expand Up @@ -308,6 +331,21 @@ All servers use random ports locally or fixed port (`19432`) in remote mode.

Runs as a separate service on port `19433` (self-hosted) or as a Cloudflare Worker (hosted).

### Room Service (`apps/room-service/`)

Live-collaboration rooms for encrypted multi-user annotation. Zero-knowledge: the Worker + Durable Object stores and relays ciphertext only. Clients hold the room secret in the URL fragment and derive `authKey`/`eventKey`/`presenceKey`/`adminKey` locally.

| Endpoint | Method | Purpose |
| --------------------- | ------ | ------------------------------------------ |
| `/` | GET | Landing page for room creation from uploaded document. Serves the same `index.html` shell; `entry.tsx` path switch renders `LandingPage` (lazy-loaded). |
| `/health` | GET | Worker liveness probe |
| `/c/:roomId` | GET | Room SPA shell — serves the built editor bundle. Response carries CSP, `Cache-Control: no-store`, `Referrer-Policy: no-referrer`. |
| `/api/rooms` | POST | Create room. Body: `{ roomId, roomVerifier, adminVerifier, initialSnapshotCiphertext, expiresInDays? }`. Returns `201` on success; `409` on duplicate. |
| `/api/fetch-markdown` | POST | URL-to-markdown proxy. Body: `{ url }`. Returns `{ markdown, source }`. |
| `/ws/:roomId` | GET | WebSocket upgrade into the room Durable Object. |

Protocol contract lives in `packages/shared/collab/`; the Worker/DO never imports client-only URL helpers.

## Plan Version History

Every plan is automatically saved to `~/.plannotator/history/{project}/{slug}/` on arrival, before the user sees the UI. Versions are numbered sequentially (`001.md`, `002.md`, etc.). The slug is derived from the plan's first `# Heading` + today's date via `generateSlug()`, scoped by project name (git repo or cwd). Same heading on the same day = same slug = same plan being iterated on. Identical resubmissions are deduplicated (no new file if content matches the latest version).
Expand Down
189 changes: 189 additions & 0 deletions apps/collab-agent/AGENT_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Plannotator Live Rooms — Agent Instructions

This document is prose an AI agent (Claude Code, Codex, OpenCode,
Junie, or another) should have in its prompt when it's being
driven to participate in a Plannotator Live Room. It explains
the identity convention, the CLI subcommand surface, and the
handful of rules that keep agent participation well-behaved.

## 1. Identity

Your identity in the room follows the pattern:

```
<user>-agent-<type>
```

Examples: `swift-falcon-tater-agent-claude`,
`alice-agent-codex`.

- `<user>` is the human you're acting on behalf of. If you've
been given their Plannotator identity (a "tater name" like
`swift-falcon-tater`), use it verbatim.
- `<type>` is one of: `claude`, `codex`, `opencode`, `junie`,
`other`. Use `other` when you don't fit any of the explicit
kinds — it's a legal value, not a fallback error.

You pass these as `--user` and `--type` on every CLI invocation;
the CLI assembles the full identity string and refuses to run if
either is missing or malformed.

Room participants see your identity in their avatar row and as
the label on your cursor. A small `⚙` marker appears next to the
identity on both surfaces so observers can tell you're an agent,
not a human teammate.

## 2. Joining and staying visible

The V1 room protocol has no participant roster. Peers appear on
one another's screens **only after presence is received**. A
client that just connects and stays silent is invisible.

Two subcommands handle this correctly:

- `join` — connect, emit initial presence, heartbeat presence on
a 10s cadence, stream room events to stdout until Ctrl-C. Use
this when you need to be present while you think or wait.
- `demo` — a showcase walk; not for real work.

Short one-shot reads (`read-plan`, `read-annotations`,
`read-presence`) emit presence exactly once before they print and
exit. You briefly flash into the observer's avatar row, then
disappear.

Do **not** implement your own WebSocket or presence loop. The
CLI is the supported entry point.

## 3. Reading the plan

```
bun run apps/collab-agent/index.ts read-plan \
--url "<full room URL including #key=...>" \
--user <name> --type <kind>
```

Add `--with-block-ids` to get each block prefixed with
`[block:<id>]`. You need those ids if you plan to comment.

Block ids are **derived from the markdown** — the CLI uses the
same parser the browser uses, so the ids you read here are
byte-identical to what the observer sees in their DOM.

## 4. Reading existing annotations

```
bun run apps/collab-agent/index.ts read-annotations \
--url "..." --user <name> --type <kind>
```

Prints the full `RoomAnnotation[]` array as pretty JSON. Fields:
`id`, `blockId`, `startOffset`, `endOffset`, `type`, `text`,
`originalText`, `createdA`, `author`.

## 5. Reading recent presence

```
bun run apps/collab-agent/index.ts read-presence \
--url "..." --user <name> --type <kind>
```

Prints `remotePresence` as JSON keyed by opaque per-connection
client ids. **This is NOT a participant roster.** It is
"peers who've emitted presence in the last 30 seconds." A user
who's connected but idle (not moving their mouse) will NOT
appear. Do not infer "who's in the room" from this call.

## 6. Posting a comment

Block-level only in V1.

```
bun run apps/collab-agent/index.ts comment \
--url "..." --user <name> --type <kind> \
--block <blockId> --text "<your comment>"
```

The annotation targets the entire block — its full content is the
"original text", and your `--text` becomes the comment body. Do
**not** attempt to select a sub-range of text. The V1 agent flow
does not support inline text-range targeting; the
`/api/external-annotations` inline-text matcher that some agents
may have used before is known to fail silently on markdown /
whitespace / NBSP / block-boundary drift.

### Choosing a block id

Three ways:

1. Run `read-plan --with-block-ids` to see the plan interleaved
with block markers.
2. Run `read-annotations` to see block ids on annotations other
agents or humans have already left.
3. Run `comment --list-blocks` (with `--url/--user/--type`) to
print a JSON array of `{ id, type, content }` for every block
and exit without posting.

Pick a block whose `content` matches what you want to comment on.

### Referencing specific wording

If your comment is about specific wording within a block, quote
the wording **in the comment body**, not as an anchor:

```
--text 'The phrase "as soon as possible" is ambiguous — what is the deadline?'
```

Do not try to select only `"as soon as possible"`. Select the
whole block, and put the phrase in prose.

### Exit codes

- `0` — comment echoed back from the server (confirmed posted).
- `1` — snapshot / echo timeout, unknown block id, or server
rejected the op (e.g. the room was deleted).
- `2` — argv or usage error (missing flag, bad --type, etc.).

## 7. Demo mode

```
bun run apps/collab-agent/index.ts demo \
--url "..." --user <name> --type <kind> \
--duration 120
```

Walks heading blocks in order, anchors the cursor to each, posts
a comment per heading. For showcase only — not a real
participation pattern. Pass `--dry-run` to do the cursor walk
without posting.

## 8. Rules and limits

- **Never run as admin.** The CLI strips any `#admin=<secret>`
fragment from the URL by default and warns on stderr. There is
no opt-in flag. Agents do not perform delete.
- **No image attachments.** V1 room annotations do not carry
images. If you need to share an image, the flow is via the
local editor's import path, not via the agent CLI.
- **Room annotations are server-authoritative.** Your
`sendAnnotationAdd` queues a local op; the server has the
final say. The `comment` subcommand waits for the echo before
exiting 0.
- **Text appears to peers after server echo.** Your comment
doesn't appear in your own `read-annotations` output until it
round-trips.

## 9. Troubleshooting

- **`Missing --url` / `Missing --user` / `Missing --type`** —
argv check. Add the missing flag.
- **`Timed out waiting for snapshot after 10000ms`** — the URL
parsed but the connection never received the initial
encrypted snapshot. Check the URL fragment is intact
(`#key=<secret>`) and the room service is reachable.
- **`unknown --block "<id>"`** — the block id you passed isn't
in the current plan. Run `comment --list-blocks` to see the
valid set; re-run with a matching id.
- **`<code>: <message>`** on a comment — server-side mutation
rejection. The message names the reason; wait and retry or
target a different room.
Loading
Loading