Skip to content

Commit fbd4ee4

Browse files
backnotpropclaude
andauthored
Remove non-critical welcome dialogs (#280)
* chore: remove non-critical welcome dialogs Remove UI Features Setup, Plan Diff Marketing, and What's New v0.11.0 dialogs from the first-run cascade. Only the Permission Mode Setup dialog remains as it's the only one that affects agent behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract demo plan content to separate file Moves the 334-line PLAN_CONTENT string from App.tsx to demoPlan.ts to reduce clutter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0ad1cce commit fbd4ee4

8 files changed

Lines changed: 341 additions & 787 deletions

File tree

packages/editor/App.tsx

Lines changed: 7 additions & 396 deletions
Large diffs are not rendered by default.

packages/editor/demoPlan.ts

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
export const DEMO_PLAN_CONTENT = `# Implementation Plan: Real-time Collaboration
2+
3+
## Overview
4+
Add real-time collaboration features to the editor using **[WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)** and *[operational transforms](https://en.wikipedia.org/wiki/Operational_transformation)*.
5+
6+
## Phase 1: Infrastructure
7+
8+
### WebSocket Server
9+
Set up a WebSocket server to handle concurrent connections:
10+
11+
\`\`\`typescript
12+
const server = new WebSocketServer({ port: 8080 });
13+
14+
server.on('connection', (socket, request) => {
15+
const sessionId = generateSessionId();
16+
sessions.set(sessionId, socket);
17+
18+
socket.on('message', (data) => {
19+
broadcast(sessionId, data);
20+
});
21+
});
22+
\`\`\`
23+
24+
### Client Connection
25+
- Establish persistent connection on document load
26+
- Initialize WebSocket with authentication token
27+
- Set up heartbeat ping/pong every 30 seconds
28+
- Handle connection state changes (connecting, open, closing, closed)
29+
- Implement reconnection logic with exponential backoff
30+
- Start with 1 second delay
31+
- Double delay on each retry (max 30 seconds)
32+
- Reset delay on successful connection
33+
- Handle offline state gracefully
34+
- Queue local changes in IndexedDB
35+
- Show offline indicator in UI
36+
- Sync queued changes on reconnect
37+
38+
### Database Schema
39+
40+
\`\`\`sql
41+
CREATE TABLE documents (
42+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
43+
title VARCHAR(255) NOT NULL,
44+
content JSONB NOT NULL DEFAULT '{}',
45+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
46+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
47+
);
48+
49+
CREATE TABLE collaborators (
50+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
51+
document_id UUID REFERENCES documents(id) ON DELETE CASCADE,
52+
user_id UUID NOT NULL,
53+
role VARCHAR(50) DEFAULT 'editor',
54+
cursor_position JSONB,
55+
last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
56+
);
57+
58+
CREATE INDEX idx_collaborators_document ON collaborators(document_id);
59+
\`\`\`
60+
61+
### Architecture
62+
63+
\`\`\`mermaid
64+
flowchart LR
65+
subgraph Client["Client Browser"]
66+
UI[React UI] --> OT[OT Engine]
67+
OT <--> WS[WebSocket Client]
68+
end
69+
70+
subgraph Server["Backend"]
71+
WSS[WebSocket Server] <--> OTS[OT Transform]
72+
OTS <--> DB[(PostgreSQL)]
73+
end
74+
75+
WS <--> WSS
76+
\`\`\`
77+
78+
### Service Dependencies (Graphviz)
79+
80+
\`\`\`graphviz
81+
digraph CollaborationStack {
82+
rankdir=LR;
83+
node [shape=box, style="rounded"];
84+
85+
Browser [label="Client Browser"];
86+
API [label="WebSocket API"];
87+
OT [label="OT Engine"];
88+
Redis [label="Presence Cache"];
89+
Postgres [label="PostgreSQL"];
90+
91+
Browser -> API;
92+
API -> OT;
93+
OT -> Redis;
94+
OT -> Postgres;
95+
}
96+
\`\`\`
97+
98+
## Phase 2: Operational Transforms
99+
100+
> The key insight is that we need to transform operations against concurrent operations to maintain consistency.
101+
102+
Key requirements:
103+
- Transform insert against insert
104+
- Same position: use user ID for deterministic ordering
105+
- Different positions: adjust offset of later operation
106+
- Transform insert against delete
107+
- Insert before delete: no change needed
108+
- Insert inside deleted range: special handling required
109+
- Option A: Move insert to delete start position
110+
- Option B: Discard the insert entirely
111+
- Insert after delete: adjust insert position
112+
- Transform delete against delete
113+
- Non-overlapping: adjust positions
114+
- Overlapping: merge or split operations
115+
- Maintain cursor positions across transforms
116+
- Track cursor as a zero-width insert operation
117+
- Update cursor position after each transform
118+
119+
### Transform Implementation
120+
121+
\`\`\`typescript
122+
interface Operation {
123+
type: 'insert' | 'delete';
124+
position: number;
125+
content?: string;
126+
length?: number;
127+
userId: string;
128+
timestamp: number;
129+
}
130+
131+
class OperationalTransform {
132+
private pendingOps: Operation[] = [];
133+
private history: Operation[] = [];
134+
135+
transform(op1: Operation, op2: Operation): [Operation, Operation] {
136+
if (op1.type === 'insert' && op2.type === 'insert') {
137+
if (op1.position <= op2.position) {
138+
return [op1, { ...op2, position: op2.position + (op1.content?.length || 0) }];
139+
} else {
140+
return [{ ...op1, position: op1.position + (op2.content?.length || 0) }, op2];
141+
}
142+
}
143+
144+
if (op1.type === 'delete' && op2.type === 'delete') {
145+
// Complex delete vs delete transformation
146+
const op1End = op1.position + (op1.length || 0);
147+
const op2End = op2.position + (op2.length || 0);
148+
149+
if (op1End <= op2.position) {
150+
return [op1, { ...op2, position: op2.position - (op1.length || 0) }];
151+
}
152+
// ... more cases
153+
}
154+
155+
return [op1, op2];
156+
}
157+
158+
apply(doc: string, op: Operation): string {
159+
if (op.type === 'insert') {
160+
return doc.slice(0, op.position) + op.content + doc.slice(op.position);
161+
} else {
162+
return doc.slice(0, op.position) + doc.slice(op.position + (op.length || 0));
163+
}
164+
}
165+
}
166+
\`\`\`
167+
168+
## Phase 3: UI Updates
169+
170+
1. Show collaborator cursors in real-time
171+
- Render cursor as colored vertical line
172+
- Add name label above cursor
173+
- Animate cursor movement smoothly
174+
2. Display presence indicators
175+
- Avatar stack in header
176+
- Dropdown with full collaborator list
177+
- Show online/away status
178+
- Display last activity time
179+
- Allow @mentioning collaborators
180+
3. Add conflict resolution UI
181+
- Highlight conflicting regions
182+
- Show diff comparison panel
183+
- Provide merge options:
184+
- Accept mine
185+
- Accept theirs
186+
- Manual merge
187+
4. Implement undo/redo stack per user
188+
- Track operations by user ID
189+
- Allow undoing only own changes
190+
- Show undo history in sidebar
191+
192+
### React Component for Cursors
193+
194+
\`\`\`tsx
195+
import React, { useEffect, useState } from 'react';
196+
import { useCollaboration } from '../hooks/useCollaboration';
197+
198+
interface CursorOverlayProps {
199+
documentId: string;
200+
containerRef: React.RefObject<HTMLDivElement>;
201+
}
202+
203+
export const CursorOverlay: React.FC<CursorOverlayProps> = ({
204+
documentId,
205+
containerRef
206+
}) => {
207+
const { collaborators, currentUser } = useCollaboration(documentId);
208+
const [positions, setPositions] = useState<Map<string, DOMRect>>(new Map());
209+
210+
useEffect(() => {
211+
const updatePositions = () => {
212+
const newPositions = new Map<string, DOMRect>();
213+
collaborators.forEach(collab => {
214+
if (collab.userId !== currentUser.id && collab.cursorPosition) {
215+
const rect = getCursorRect(containerRef.current, collab.cursorPosition);
216+
if (rect) newPositions.set(collab.userId, rect);
217+
}
218+
});
219+
setPositions(newPositions);
220+
};
221+
222+
const interval = setInterval(updatePositions, 50);
223+
return () => clearInterval(interval);
224+
}, [collaborators, currentUser, containerRef]);
225+
226+
return (
227+
<>
228+
{Array.from(positions.entries()).map(([userId, rect]) => (
229+
<div
230+
key={userId}
231+
className="absolute pointer-events-none transition-all duration-75"
232+
style={{
233+
left: rect.left,
234+
top: rect.top,
235+
height: rect.height,
236+
}}
237+
>
238+
<div className="w-0.5 h-full bg-blue-500 animate-pulse" />
239+
<div className="absolute -top-5 left-0 px-1.5 py-0.5 bg-blue-500
240+
text-white text-xs rounded whitespace-nowrap">
241+
{collaborators.find(c => c.userId === userId)?.userName}
242+
</div>
243+
</div>
244+
))}
245+
</>
246+
);
247+
};
248+
\`\`\`
249+
250+
### Configuration
251+
252+
\`\`\`json
253+
{
254+
"collaboration": {
255+
"enabled": true,
256+
"maxCollaborators": 10,
257+
"cursorColors": ["#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6"],
258+
"syncInterval": 100,
259+
"reconnect": {
260+
"maxAttempts": 5,
261+
"backoffMultiplier": 1.5,
262+
"initialDelay": 1000
263+
}
264+
}
265+
}
266+
\`\`\`
267+
268+
---
269+
270+
## Pre-launch Checklist
271+
272+
- [ ] Infrastructure ready
273+
- [x] WebSocket server deployed
274+
- [x] Database migrations applied
275+
- [ ] Load balancer configured
276+
- [ ] SSL certificates installed
277+
- [ ] Health checks enabled
278+
- [ ] /health endpoint returns 200
279+
- [ ] /ready endpoint checks DB connection
280+
- [ ] Primary database
281+
- [ ] Read replicas
282+
- [ ] us-east-1 replica
283+
- [ ] eu-west-1 replica
284+
- [ ] Security audit complete
285+
- [x] Authentication flow reviewed
286+
- [ ] Rate limiting implemented
287+
- [x] 100 req/min for anonymous users
288+
- [ ] 1000 req/min for authenticated users
289+
- [ ] Input sanitization verified
290+
- [x] Documentation updated
291+
- [x] API reference generated
292+
- [x] Integration guide written
293+
- [ ] Video tutorials recorded
294+
295+
### API Endpoints
296+
297+
| Method | Endpoint | Description | Auth |
298+
|--------|----------|-------------|------|
299+
| GET | /api/documents | List all documents | Required |
300+
| POST | /api/documents | Create new document | Required |
301+
| GET | /api/documents/:id | Fetch document | Required |
302+
| PUT | /api/documents/:id | Update document | Owner/Editor |
303+
| DELETE | /api/documents/:id | Delete document | Owner only |
304+
| POST | /api/documents/:id/share | Share document | Owner only |
305+
| GET | /api/documents/:id/collaborators | List collaborators | Required |
306+
307+
### Performance Targets
308+
309+
| Metric | Target | Current | Status |
310+
|--------|--------|---------|--------|
311+
| WebSocket latency | < 50ms | 42ms | On track |
312+
| Time to first cursor | < 200ms | 310ms | **At risk** |
313+
| Concurrent users/doc | 50 | 25 | In progress |
314+
| Operation transform | < 5ms | 3ms | On track |
315+
| Reconnect time | < 2s | 1.8s | On track |
316+
317+
### Mixed List Styles
318+
319+
* Asterisk item at level 0
320+
- Dash item at level 1
321+
* Asterisk at level 2
322+
- Dash at level 3
323+
* Asterisk at level 4
324+
- Maximum reasonable depth
325+
1. Numbered item
326+
- Sub-bullet under numbered
327+
- Another sub-bullet
328+
1. Nested numbered list
329+
2. Second nested number
330+
331+
---
332+
333+
**Target:** Ship MVP in next sprint
334+
`;

0 commit comments

Comments
 (0)