From bf07f1cb4af0189859826a55abf29186a49190c8 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Sun, 18 Jan 2026 14:38:32 +0100 Subject: [PATCH] Added new github integration tasks --- dev-docs/reference/LEARNINGS.md | 81 + .../phase-3-editor-ux-overhaul/PROGRESS.md | 47 +- .../CHANGELOG.md | 262 ++ .../CHECKLIST.md | 176 +- .../README.md | 279 ++ .../GIT-006-server-infrastructure/README.md | 330 ++ .../GIT-007-webrtc-collaboration/README.md | 342 ++ .../GIT-008-notification-system/README.md | 326 ++ .../GIT-009-community-ui/README.md | 303 ++ .../GIT-010-session-discovery/README.md | 357 ++ .../GIT-011-integration-polish/README.md | 344 ++ ...-11-Live-Collaboration-Community-System.md | 2894 +++++++++++++++++ ...-GIT-11-Part-2-Community-UI-Integration.md | 1691 ++++++++++ .../GIT-INTEGRATION-STRATEGY.md | 292 ++ .../README.md | 234 +- .../CHANGELOG.md | 246 ++ .../README.md | 197 ++ .../preview/launcher/Launcher/Launcher.tsx | 23 +- .../launcher/Launcher/LauncherContext.tsx | 7 +- .../launcher/Launcher/hooks/useGitHubRepos.ts | 337 ++ .../launcher/Launcher/views/GitHubRepos.tsx | 450 +++ .../src/pages/ProjectsPage/ProjectsPage.tsx | 347 +- .../editor/src/services/GitHubOAuthService.ts | 320 +- .../src/services/github/GitHubClient.ts | 234 +- .../editor/src/services/github/GitHubTypes.ts | 20 + .../editor/src/utils/LocalProjectsModel.ts | 23 +- .../views/panels/GitHubPanel/GitHubPanel.tsx | 85 +- .../ConnectToGitHub.module.scss | 459 +++ .../ConnectToGitHub/ConnectToGitHubView.tsx | 299 ++ .../ConnectToGitHub/CreateRepoModal.tsx | 174 + .../ConnectToGitHub/SelectRepoModal.tsx | 226 ++ .../components/ConnectToGitHub/index.ts | 3 + .../SyncToolbar/SyncToolbar.module.scss | 161 + .../components/SyncToolbar/SyncToolbar.tsx | 150 + .../components/SyncToolbar/index.ts | 1 + .../GitHubPanel/hooks/useGitHubRepository.ts | 179 +- .../GitHubPanel/hooks/useGitSyncStatus.ts | 247 ++ .../panels/GitHubPanel/hooks/useIssues.ts | 22 +- .../GitHubPanel/hooks/usePullRequests.ts | 21 +- .../sections/CredentialsSection.tsx | 71 +- .../src/main/github-oauth-handler.js | 29 +- packages/noodl-editor/src/main/main.js | 8 +- packages/noodl-git/src/core/git-error.ts | 112 +- packages/noodl-git/src/git.ts | 8 +- 44 files changed, 12015 insertions(+), 402 deletions(-) create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-005-community-infrastructure/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-006-server-infrastructure/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-007-webrtc-collaboration/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-008-notification-system/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-009-community-ui/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-010-session-discovery/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-011-integration-polish/README.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Live-Collaboration-Community-System.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Part-2-Community-UI-Integration.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-INTEGRATION-STRATEGY.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002C-github-clone-and-connect/CHANGELOG.md create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002C-github-clone-and-connect/README.md create mode 100644 packages/noodl-core-ui/src/preview/launcher/Launcher/hooks/useGitHubRepos.ts create mode 100644 packages/noodl-core-ui/src/preview/launcher/Launcher/views/GitHubRepos.tsx create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/ConnectToGitHub/ConnectToGitHub.module.scss create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/ConnectToGitHub/ConnectToGitHubView.tsx create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/ConnectToGitHub/CreateRepoModal.tsx create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/ConnectToGitHub/SelectRepoModal.tsx create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/ConnectToGitHub/index.ts create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/SyncToolbar/SyncToolbar.module.scss create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/SyncToolbar/SyncToolbar.tsx create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/components/SyncToolbar/index.ts create mode 100644 packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/hooks/useGitSyncStatus.ts diff --git a/dev-docs/reference/LEARNINGS.md b/dev-docs/reference/LEARNINGS.md index 4439550..1527dba 100644 --- a/dev-docs/reference/LEARNINGS.md +++ b/dev-docs/reference/LEARNINGS.md @@ -4,6 +4,87 @@ This document captures important discoveries and gotchas encountered during Open --- +## ✅ VersionControlPanel is Already React! (Jan 18, 2026) + +### The Good News: No jQuery Rewrite Needed + +**Context**: Phase 3 TASK-002B GitHub Advanced Integration - Concerned that extending the existing VersionControlPanel with GitHub features would require a jQuery-to-React rewrite. + +**GREAT NEWS**: The entire VersionControlPanel is already **100% modern React**! + +**What Was Verified**: + +``` +VersionControlPanel/ +├── VersionControlPanel.tsx ✅ React (useState, useEffect, useRef) +├── context/ ✅ React Context API +│ └── index.tsx +├── components/ +│ ├── LocalChanges.tsx ✅ React functional component +│ ├── History.tsx ✅ React functional component +│ ├── HistoryCommitDiff.tsx ✅ React functional component +│ ├── CommitChangesDiff.tsx ✅ React functional component +│ ├── DiffList.tsx ✅ React functional component +│ ├── BranchList.tsx ✅ React functional component +│ ├── BranchMerge.tsx ✅ React functional component +│ ├── MergeConflicts.tsx ✅ React functional component +│ └── Stashes.tsx ✅ React functional component +└── hooks/ + └── useShowComponentDiffDocument.ts ✅ React hook +``` + +**Modern Patterns Already in Use**: + +- React hooks (useState, useEffect, useRef) +- Context API (VersionControlContext) +- TypeScript throughout +- @noodl-core-ui design system components +- NO jQuery anywhere (except PopupLayer which is separate system) + +**Visual Diff System Already Works**: +The "green nodes for additions, red for deletions" visual diff is already implemented: + +1. Click any commit in History tab +2. `HistoryCommitDiff.tsx` shows the diff +3. `CommitChangesDiff.tsx` fetches project.json diff +4. `DiffList.tsx` renders component-level changes +5. `useShowComponentDiffDocument` opens visual node graph diff + +**Integration Strategy**: +Instead of creating a separate GitHubPanel, extend VersionControlPanel: + +```typescript +// Just add new tabs to existing panel + }, // Existing + { id: 'history', content: }, // Existing + { id: 'issues', content: }, // NEW (11-16 hours) + { id: 'prs', content: } // NEW + ]} +/> +``` + +**Why This Matters**: + +- **No rewrite needed** - Just add React components to existing React panel +- **Shared context** - Can extend VersionControlContext with GitHub state +- **Visual diffs work** - The killer feature is already implemented +- **Familiar UX** - Users already know the Version Control panel + +**Time Saved**: Estimated 50+ hours saved vs creating separate panel or rewriting jQuery. + +**Documentation**: Created `GIT-INTEGRATION-STRATEGY.md` with full implementation plan. + +**Location**: + +- Panel: `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/` +- Strategy: `dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-INTEGRATION-STRATEGY.md` + +**Keywords**: VersionControlPanel, React, GitHub integration, visual diff, DiffList, History, no jQuery, extend existing panel + +--- + ## 🏗️ CRITICAL ARCHITECTURE PATTERNS These fundamental patterns apply across ALL Noodl development. Understanding them prevents hours of debugging. diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/PROGRESS.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/PROGRESS.md index 7cf6a61..e6b8268 100644 --- a/dev-docs/tasks/phase-3-editor-ux-overhaul/PROGRESS.md +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/PROGRESS.md @@ -1,6 +1,6 @@ # Phase 3: Editor UX Overhaul - Progress Tracker -**Last Updated:** 2026-01-14 +**Last Updated:** 2026-01-18 **Overall Status:** 🟡 In Progress --- @@ -9,27 +9,28 @@ | Metric | Value | | ------------ | ------- | -| Total Tasks | 9 | +| Total Tasks | 10 | | Completed | 3 | -| In Progress | 1 | +| In Progress | 2 | | Not Started | 5 | -| **Progress** | **33%** | +| **Progress** | **30%** | --- ## Task Status -| Task | Name | Status | Notes | -| --------- | ----------------------- | -------------- | --------------------------------------------- | -| TASK-001 | Dashboard UX Foundation | 🟢 Complete | Tabbed navigation done | -| TASK-001B | Launcher Fixes | 🟢 Complete | All 4 subtasks implemented | -| TASK-002 | GitHub Integration | 🟢 Complete | OAuth + basic features done | -| TASK-002B | GitHub Advanced | 🟡 In Progress | GIT-004A complete, 5 subtasks remaining | -| TASK-003 | Shared Component System | 🔴 Not Started | Prefab system refactor | -| TASK-004 | AI Project Creation | 🔴 Not Started | AI scaffolding feature | -| TASK-005 | Deployment Automation | 🔴 Not Started | Planning docs only, no implementation | -| TASK-006 | Expressions Overhaul | 🔴 Not Started | Enhanced expression nodes | -| TASK-007 | App Config | 🟡 In Progress | Runtime ✅, UI mostly done (Monaco debugging) | +| Task | Name | Status | Notes | +| --------- | --------------------------- | -------------- | --------------------------------------------------------- | +| TASK-001 | Dashboard UX Foundation | 🟢 Complete | Tabbed navigation done | +| TASK-001B | Launcher Fixes | 🟢 Complete | All 4 subtasks implemented | +| TASK-002 | GitHub Integration | 🟢 Complete | OAuth + basic features done | +| TASK-002B | GitHub Advanced Integration | 🟡 In Progress | Part 1: GIT-004A-C done. Part 2: GIT-005-011 docs created | +| TASK-002C | GitHub Clone & Connect | 🟡 In Progress | Clone from launcher flow | +| TASK-003 | Shared Component System | 🔴 Not Started | Prefab system refactor | +| TASK-004 | AI Project Creation | 🔴 Not Started | AI scaffolding feature | +| TASK-005 | Deployment Automation | 🔴 Not Started | Planning docs only, no implementation | +| TASK-006 | Expressions Overhaul | 🔴 Not Started | Enhanced expression nodes | +| TASK-007 | App Config | 🟡 In Progress | Runtime ✅, UI mostly done (Monaco debugging) | --- @@ -43,13 +44,15 @@ ## Recent Updates -| Date | Update | -| ---------- | ------------------------------------------------------ | -| 2026-01-14 | TASK-002B GIT-004A complete (GitHub Client Foundation) | -| 2026-01-07 | Audit completed: corrected TASK-001B, TASK-005 status | -| 2026-01-07 | Added TASK-006 and TASK-007 to tracking | -| 2026-01-07 | TASK-008 moved to Phase 6 (UBA) | -| 2026-01-07 | TASK-000 moved to Phase 9 (Styles) | +| Date | Update | +| ---------- | ------------------------------------------------------------------------------- | +| 2026-01-18 | TASK-002B: Created GIT-005-011 task docs (Live Collaboration & Multi-Community) | +| 2026-01-18 | TASK-002B scope expanded to 501-662 hours total | +| 2026-01-14 | TASK-002B GIT-004A complete (GitHub Client Foundation) | +| 2026-01-07 | Audit completed: corrected TASK-001B, TASK-005 status | +| 2026-01-07 | Added TASK-006 and TASK-007 to tracking | +| 2026-01-07 | TASK-008 moved to Phase 6 (UBA) | +| 2026-01-07 | TASK-000 moved to Phase 9 (Styles) | --- diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHANGELOG.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHANGELOG.md index 1cd8631..33666ba 100644 --- a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHANGELOG.md +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHANGELOG.md @@ -329,6 +329,176 @@ Built complete GitHub Pull Requests panel following the same patterns as Issues --- +## [2026-01-16] - OAuth Flow Bug Fixes - Complete ✅ + +### Summary + +Fixed critical bugs preventing GitHub OAuth from working. The OAuth flow was broken due to two separate OAuth implementations that weren't communicating properly. + +### Root Causes Identified + +1. **Git Path Null Error**: `git.openRepository()` didn't handle when `open()` returns null (not a git repo) +2. **Disconnected OAuth Flows**: Two separate OAuth implementations existed: + - `GitHubOAuthService.ts` (renderer) - Generated PKCE state, opened browser + - `github-oauth-handler.js` (main) - Had separate state, handled callback + - These didn't communicate - OAuth callback went to main process, but renderer never received it + +### Files Modified + +**Bug Fix 1 - Git Null Path:** + +- `packages/noodl-git/src/git.ts` - Added null check in `openRepository()` to throw clear error instead of crashing + +**Bug Fix 2 - OAuth Flow:** + +- `packages/noodl-editor/src/editor/src/services/GitHubOAuthService.ts` - Complete rewrite to: + - Use IPC to get auth URL from main process (reuses main's state) + - Listen for `github-oauth-complete` and `github-oauth-error` IPC events + - Properly receive and store tokens after OAuth callback + +**Bug Fix 3 - Error Handling:** + +- `packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/hooks/useGitHubRepository.ts` - Added try/catch around `git.openRepository()` to gracefully handle non-git projects + +**Bug Fix 4 - Initialization:** + +- `packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/GitHubPanel.tsx` - Added: + - `GitHubOAuthService.initialize()` call on mount to restore saved tokens + - `useEventListener` to react to auth state changes + - Loading state while initializing + +### Technical Notes + +**OAuth Flow Now:** + +1. User clicks "Connect GitHub Account" +2. GitHubOAuthService calls `ipcRenderer.invoke('github-oauth-start')` - main generates state +3. Main returns auth URL, renderer opens in browser +4. User authorizes, GitHub redirects to `noodl://github-callback` +5. Main process receives callback, validates state (its own), exchanges code for token +6. Main sends `github-oauth-complete` IPC event with token + user +7. GitHubOAuthService receives event, stores token, updates state +8. GitHubPanel re-renders as connected + +**Token Storage:** + +- Main process uses `jsonstorage` + Electron `safeStorage` for encryption +- Token persisted across sessions +- `initialize()` loads saved token on app start + +### Existing Infrastructure Verified + +The following was already correctly implemented in main.js: + +- `initializeGitHubOAuthHandlers(app)` - Protocol handler registered +- `github-save-token` / `github-load-token` / `github-clear-token` IPC handlers +- Token encryption using Electron's safeStorage + +### Error Messages Improved + +- "Not a git repository: /path" instead of cryptic "path must be string, received null" +- Console logging for OAuth flow stages for debugging + +### Testing Required + +- [ ] Click "Connect GitHub Account" and complete OAuth flow +- [ ] Verify token is stored and user info displayed +- [ ] Close and reopen app, verify session is restored +- [ ] Test with non-git project (should show graceful error) +- [ ] Test with GitHub repo (should detect owner/repo) + +--- + +## [2026-01-16] - Session 2: Critical Bug Fixes & Unification - Complete ✅ + +### Summary + +Second bug-fix session addressing infinite loops, jsonstorage errors, IPC targeting issues, and unifying OAuth across launcher and editor. + +### Bugs Fixed + +**Bug 1: `jsonstorage.getSync` doesn't exist** + +- **File:** `packages/noodl-editor/src/main/main.js` (line ~672) +- **Fix:** The jsonstorage module only has async `get(key, callback)`. Changed to Promise wrapper. + +```javascript +const stored = await new Promise((resolve) => { + jsonstorage.get('github.token', (data) => resolve(data)); +}); +``` + +**Bug 2: IPC event not reaching renderer** + +- **File:** `packages/noodl-editor/src/main/github-oauth-handler.js` +- **Fix:** Was sending to `windows[0]` which might be viewer/floating window, not editor. Now broadcasts to ALL windows. + +```javascript +windows.forEach((win, index) => { + win.webContents.send('github-oauth-complete', result); +}); +``` + +**Bug 3: Infinite loop in useIssues hook** + +- **File:** `packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/hooks/useIssues.ts` +- **Root Cause:** `filters` object in useEffect deps creates new reference on every render +- **Fix:** Use `useRef` for filters and `JSON.stringify(filters)` for dependency comparison + +```javascript +const filtersRef = useRef(filters); +filtersRef.current = filters; +const filtersKey = JSON.stringify(filters); +useEffect(() => { ... }, [owner, repo, filtersKey, enabled, refetch]); +``` + +**Bug 4: Infinite loop in usePullRequests hook** + +- **File:** `packages/noodl-editor/src/editor/src/views/panels/GitHubPanel/hooks/usePullRequests.ts` +- **Fix:** Same pattern as useIssues - ref for filters, JSON serialization for deps + +### New Features + +**Launcher OAuth Integration** + +- **File:** `packages/noodl-editor/src/editor/src/pages/ProjectsPage/ProjectsPage.tsx` +- Added state: `githubIsAuthenticated`, `githubIsConnecting`, `githubUser` +- Added handlers: `handleGitHubConnect`, `handleGitHubDisconnect` +- Listens for auth state changes via `useEventListener` +- Launcher button now initiates real OAuth flow + +**CredentialsSection OAuth Migration** + +- **File:** `packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/components/GitProviderPopout/sections/CredentialsSection.tsx` +- Migrated from old `GitHubAuth` service to unified `GitHubOAuthService` +- Now shares token storage with GitHubPanel and Launcher + +### Testing Results + +| Test | Result | +| ----------------------------------- | ---------------------------- | +| OAuth from GitHubPanel in project | ✅ Works | +| OAuth from Launcher header button | ✅ Works | +| OAuth from CredentialsSection | ✅ Works (migrated) | +| Token persistence across restart | ✅ Works (without clean:all) | +| Issues/PRs single request (no loop) | ✅ Works | +| 404 error handling (no loop) | ✅ Works | + +### Technical Notes + +- All three OAuth entry points now use `GitHubOAuthService.instance` +- Token stored via IPC (`github-save-token`) in main process using Electron's safeStorage +- `clean:all` will wipe tokens (expected - wipes electron-store data) + +### Future Task Created + +Created `TASK-002C-github-clone-and-connect/README.md` documenting two new features: + +- Subtask A: Clone Noodl projects from GitHub (launcher view) +- Subtask B: Connect unconnected project to GitHub (create/link repo) + +--- + ## Progress Summary | Sub-Task | Status | Started | Completed | @@ -336,6 +506,7 @@ Built complete GitHub Pull Requests panel following the same patterns as Issues | GIT-004A: OAuth & Client | ✅ Complete | 2026-01-14 | 2026-01-14 | | GIT-004B: Issues Read | ✅ Complete | 2026-01-14 | 2026-01-14 | | GIT-004C: PRs Read | ✅ Complete | 2026-01-15 | 2026-01-15 | +| OAuth Bug Fixes | ✅ Complete | 2026-01-16 | 2026-01-16 | | GIT-004D: Issues CRUD | Not Started | - | - | | GIT-004E: Component Linking | Not Started | - | - | | GIT-004F: Dashboard | Not Started | - | - | @@ -381,3 +552,94 @@ _Track user feedback during development/testing_ | Date | Feedback | Source | Action | | ---- | -------- | ------ | ------ | | - | - | - | - | + +--- + +## [2026-01-18] - VersionControlPanel Discovery + GIT-005-011 Documentation ✅ + +### Summary + +Major documentation effort creating GIT-005 through GIT-011 task specs (Live Collaboration & Multi-Community System), plus critical discovery that the existing VersionControlPanel is already 100% React with full visual diff support. + +### Key Discovery: VersionControlPanel is React! 🎉 + +**Not jQuery** - The entire VersionControlPanel is modern React: + +- React functional components with hooks (useState, useEffect, useRef) +- Context API (VersionControlContext) +- TypeScript throughout +- Uses @noodl-core-ui design system + +**Visual diff system already works**: + +- Click any commit in History tab +- See green nodes (additions), red nodes (deletions) +- Component-level diff visualization +- `DiffList.tsx`, `CommitChangesDiff.tsx` already implement this + +### Integration Strategy Created + +**Approach**: Extend VersionControlPanel with GitHub tabs instead of maintaining separate GitHubPanel. + +**Benefits**: + +- Single unified panel for all version control +- Existing visual diffs work unchanged +- No rewrite needed - just add tabs +- Estimated 11-16 hours vs 70-90 for separate panel + +**Documentation**: `GIT-INTEGRATION-STRATEGY.md` + +### Task Documentation Created (GIT-005-011) + +**Part 2: Live Collaboration & Multi-Community System** + +| Task | Name | Hours | Files Created | +| ------- | ------------------------ | ------- | -------------------------------------------- | +| GIT-005 | Community Infrastructure | 60-80 | `GIT-005-community-infrastructure/README.md` | +| GIT-006 | Server Infrastructure | 80-100 | `GIT-006-server-infrastructure/README.md` | +| GIT-007 | WebRTC Collaboration | 100-130 | `GIT-007-webrtc-collaboration/README.md` | +| GIT-008 | Notification System | 50-70 | `GIT-008-notification-system/README.md` | +| GIT-009 | Community Tab UI | 80-100 | `GIT-009-community-ui/README.md` | +| GIT-010 | Session Discovery | 50-70 | `GIT-010-session-discovery/README.md` | +| GIT-011 | Integration & Polish | 61-82 | `GIT-011-integration-polish/README.md` | + +**Total Part 2 Effort**: 431-572 hours + +### Files Created + +- `GIT-005-community-infrastructure/README.md` +- `GIT-006-server-infrastructure/README.md` +- `GIT-007-webrtc-collaboration/README.md` +- `GIT-008-notification-system/README.md` +- `GIT-009-community-ui/README.md` +- `GIT-010-session-discovery/README.md` +- `GIT-011-integration-polish/README.md` +- `GIT-INTEGRATION-STRATEGY.md` + +### Files Modified + +- `README.md` - Added Part 2 section, integration strategy reference +- `CHECKLIST.md` - Added GIT-005-011 checklists +- `PROGRESS.md` (Phase 3) - Updated task list and recent updates + +### VersionControlPanel Components Verified + +All these are modern React: + +- `LocalChanges.tsx` - Uncommitted changes +- `History.tsx` - Commit history +- `HistoryCommitDiff.tsx` - Visual commit diff +- `CommitChangesDiff.tsx` - Diff logic +- `DiffList.tsx` - Green/red node renderer +- `BranchList.tsx` - Branch management +- `BranchMerge.tsx` - Merge operations +- `MergeConflicts.tsx` - Conflict resolution +- `Stashes.tsx` - Git stash + +### Next Steps + +1. **Immediate**: Integrate Issues/PRs as tabs in VersionControlPanel +2. **Short-term**: Enhance GitProviderPopout with OAuth +3. **Medium-term**: Begin GIT-005 Community Infrastructure +4. **Long-term**: GIT-006-011 server and collaboration features diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHECKLIST.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHECKLIST.md index 401e651..2dbcc27 100644 --- a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHECKLIST.md +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/CHECKLIST.md @@ -1,9 +1,24 @@ # GIT-004: Implementation Checklist +> **⚠️ IMPORTANT: Integration Strategy Update (Jan 18, 2026)** +> +> GIT-004A-C were implemented with a separate `GitHubPanel`. However, we discovered that the existing `VersionControlPanel` is already 100% React with full visual diff support. +> +> **Going forward (GIT-004D-F):** We will integrate GitHub features into the existing VersionControlPanel as new tabs, rather than maintaining a separate panel. +> +> See **[GIT-INTEGRATION-STRATEGY.md](./GIT-INTEGRATION-STRATEGY.md)** for the full plan. +> +> **What this means for the checklist:** +> +> - GIT-004A-C: ✅ Complete (GitHubPanel code exists and works) +> - GIT-004D-F: Will move components from GitHubPanel → VersionControlPanel tabs + +--- + ## Pre-Implementation -- [ ] Review existing `packages/noodl-git/` code -- [ ] Review `VersionControlPanel/` patterns +- [x] Review existing `packages/noodl-git/` code +- [x] Review `VersionControlPanel/` patterns - [ ] Set up GitHub App in GitHub Developer Settings (for testing) - [ ] Document GitHub App creation steps for users @@ -381,17 +396,152 @@ --- -## Progress Summary +## Progress Summary - Part 1: GitHub Project Management -| Sub-Task | Status | Started | Completed | Hours | -|----------|--------|---------|-----------|-------| -| GIT-004A: OAuth & Client | Not Started | - | - | - | -| GIT-004B: Issues Read | Not Started | - | - | - | -| GIT-004C: PRs Read | Not Started | - | - | - | -| GIT-004D: Issues CRUD | Not Started | - | - | - | -| GIT-004E: Component Linking | Not Started | - | - | - | -| GIT-004F: Dashboard | Not Started | - | - | - | -| Integration & Polish | Not Started | - | - | - | +| Sub-Task | Status | Started | Completed | Hours | +| --------------------------- | ----------- | ------- | --------- | ----- | +| GIT-004A: OAuth & Client | ✅ Complete | - | - | - | +| GIT-004B: Issues Read | ✅ Complete | - | - | - | +| GIT-004C: PRs Read | ✅ Complete | - | - | - | +| GIT-004D: Issues CRUD | Not Started | - | - | - | +| GIT-004E: Component Linking | Not Started | - | - | - | +| GIT-004F: Dashboard | Not Started | - | - | - | +| Integration & Polish | Not Started | - | - | - | -**Total Estimated:** 70-90 hours +**Part 1 Estimated:** 70-90 hours +**Part 1 Actual:** - hours + +--- + +## Part 2: Live Collaboration & Multi-Community (GIT-005-011) + +### GIT-005: Community Infrastructure (60-80 hours) + +- [ ] Create `opennoodl-community` template repository +- [ ] Implement `community.json` schema and validation +- [ ] Create GitHub Actions workflows for validation +- [ ] Implement `CommunityManager` service +- [ ] Create community settings UI panel +- [ ] Add "Add Community" flow +- [ ] Add "Create Community" flow (fork template) +- [ ] Implement background sync +- [ ] Add server health monitoring +- [ ] Create community switcher UI + +### GIT-006: Server Infrastructure (80-100 hours) + +**Signaling Server:** + +- [ ] Create repository +- [ ] Implement WebSocket room management +- [ ] Implement signal relay +- [ ] Add health check/metrics +- [ ] Create Dockerfile +- [ ] Deploy to production + +**Sync Server:** + +- [ ] Create repository +- [ ] Implement Yjs WebSocket provider +- [ ] Add optional LevelDB persistence +- [ ] Create Dockerfile +- [ ] Deploy to production + +**Notification Server:** + +- [ ] Create repository +- [ ] Implement WebSocket authentication +- [ ] Implement notification storage +- [ ] Implement TTL cleanup +- [ ] Create Dockerfile +- [ ] Deploy to production + +### GIT-007: WebRTC Collaboration Client (100-130 hours) + +- [ ] Create `CollaborationManager` service +- [ ] Implement session creation (host) +- [ ] Implement session joining (guest) +- [ ] Implement signaling server communication +- [ ] Implement WebRTC peer connections +- [ ] Set up Yjs document structure +- [ ] Implement WebRTC provider (y-webrtc) +- [ ] Implement WebSocket fallback (y-websocket) +- [ ] Sync project to Yjs +- [ ] Implement cursor broadcasting +- [ ] Implement selection broadcasting +- [ ] Implement local audio/video capture +- [ ] Display remote media streams +- [ ] Create collaboration toolbar UI +- [ ] Create participant list panel + +### GIT-008: Notification System (50-70 hours) + +- [ ] Create `NotificationManager` service +- [ ] Implement WebSocket connection +- [ ] Implement notification fetching/parsing +- [ ] Create `NotificationToast` component +- [ ] Create toast queue system +- [ ] Create `NotificationCenter` panel +- [ ] Add notification badge +- [ ] Implement Electron desktop notifications +- [ ] Add notification settings + +### GIT-009: Community Tab UI (80-100 hours) + +- [ ] Create `CommunityPanel` component +- [ ] Create tab navigation +- [ ] Implement Home view +- [ ] Implement Sessions view +- [ ] Implement Components view +- [ ] Implement Learn view +- [ ] Implement Discuss view (GitHub Discussions) +- [ ] Implement Jobs view +- [ ] Add loading states and skeletons +- [ ] Add error handling + +### GIT-010: Session Discovery (50-70 hours) + +- [ ] Create `DeepLinkHandler` service +- [ ] Register `opennoodl://` protocol in Electron +- [ ] Create `SessionPreview` dialog +- [ ] Create `QuickJoinWidget` component +- [ ] Create `SessionHistoryManager` service +- [ ] Implement favorites +- [ ] Add "Copy Link" to active sessions +- [ ] Test cross-platform deep links + +### GIT-011: Integration & Polish (61-82 hours) + +- [ ] Create comprehensive test plan +- [ ] End-to-end testing of all flows +- [ ] Performance profiling and optimization +- [ ] Write user documentation +- [ ] Write community setup guide +- [ ] Create demo video +- [ ] Deploy servers to production +- [ ] Set up monitoring +- [ ] Complete launch checklist + +--- + +## Progress Summary - Part 2: Live Collaboration + +| Sub-Task | Name | Status | Hours | +| -------- | ------------------------ | ----------- | ----- | +| GIT-005 | Community Infrastructure | Not Started | - | +| GIT-006 | Server Infrastructure | Not Started | - | +| GIT-007 | WebRTC Collaboration | Not Started | - | +| GIT-008 | Notification System | Not Started | - | +| GIT-009 | Community Tab UI | Not Started | - | +| GIT-010 | Session Discovery | Not Started | - | +| GIT-011 | Integration & Polish | Not Started | - | + +**Part 2 Estimated:** 431-572 hours +**Part 2 Actual:** - hours + +--- + +## Overall Progress + +**Total Estimated:** 501-662 hours **Total Actual:** - hours diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-005-community-infrastructure/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-005-community-infrastructure/README.md new file mode 100644 index 0000000..37a3818 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-005-community-infrastructure/README.md @@ -0,0 +1,279 @@ +# GIT-005: Community Infrastructure & Template Repository + +## Overview + +**Priority:** High +**Estimated Hours:** 60-80 +**Dependencies:** GIT-004A (GitHub OAuth) +**Status:** 🔴 Not Started + +Create the foundational community infrastructure that enables users to fork and operate their own OpenNoodl communities. This includes the community template repository structure, server deployment templates, and community management system. + +--- + +## Strategic Context + +This task is part of the **Live Collaboration & Multi-Community System** (GIT-5 through GIT-11), which transforms OpenNoodl into a collaborative platform with: + +- Multi-community management (join multiple communities simultaneously) +- Live collaboration sessions with audio/video chat +- Component library discovery across communities +- Community template repository (forkable) +- Self-hosted infrastructure options + +--- + +## Technical Requirements + +### 1. Community Template Repository + +Create a new repository: `opennoodl-community` that serves as the template for all communities. + +**Repository Structure:** + +``` +opennoodl-community/ +├── .github/ +│ ├── workflows/ +│ │ ├── validate-community.yml # Validates community.json schema +│ │ ├── sync-discussions.yml # Syncs discussion metadata +│ │ ├── update-feeds.yml # Updates tutorial/showcase feeds +│ │ └── publish-sessions.yml # Updates public session list +│ └── ISSUE_TEMPLATE/ +│ ├── component-submission.yml +│ ├── tutorial-submission.yml +│ └── session-request.yml +│ +├── community.json # Community metadata (CRITICAL) +├── README.md # Community home page +├── CODE_OF_CONDUCT.md # Community guidelines +├── CONTRIBUTING.md # How to contribute +│ +├── components/ +│ ├── README.md # Component library guide +│ ├── featured.json # Curated components +│ └── registry.json # All registered components +│ +├── tutorials/ +│ ├── README.md # Learning resources hub +│ ├── beginner/ +│ │ └── index.json # Beginner tutorials metadata +│ ├── intermediate/ +│ │ └── index.json +│ └── advanced/ +│ └── index.json +│ +├── showcase/ +│ ├── README.md # Featured projects +│ ├── projects.json # Project submissions +│ └── templates/ +│ └── project-template.json +│ +├── jobs/ +│ ├── README.md # Job board guide +│ └── listings.json # Current listings (PRs to add) +│ +├── collaboration/ +│ ├── README.md # Collaboration guide +│ ├── public-sessions.json # Active public sessions +│ └── session-template.json # Template for session metadata +│ +└── config/ + ├── servers.json # Server endpoints + ├── features.json # Feature flags + └── notifications.json # Notification settings +``` + +### 2. Community Metadata Schema + +**File: `community.json`** + +This file is the single source of truth for community configuration. + +```json +{ + "$schema": "https://opennoodl.org/schemas/community.v1.json", + "version": "1.0.0", + "community": { + "id": "opennoodl-official", + "name": "OpenNoodl Official Community", + "description": "The official OpenNoodl visual programming community", + "type": "public", + "owner": { + "github": "The-Low-Code-Foundation", + "name": "Low Code Foundation", + "website": "https://opennoodl.org", + "contact": "community@opennoodl.org" + }, + "repository": "https://github.com/The-Low-Code-Foundation/opennoodl-community", + "createdAt": "2026-01-18T00:00:00Z", + "updatedAt": "2026-01-18T00:00:00Z" + }, + "servers": { + "signaling": { + "url": "wss://signal.opennoodl.org", + "healthCheck": "https://signal.opennoodl.org/health" + }, + "sync": { + "url": "wss://sync.opennoodl.org", + "healthCheck": "https://sync.opennoodl.org/health" + }, + "turn": { + "urls": ["turn:relay.opennoodl.org:3478"], + "username": "opennoodl", + "credentialType": "password" + }, + "notifications": { + "url": "wss://notify.opennoodl.org", + "healthCheck": "https://notify.opennoodl.org/health" + } + }, + "features": { + "discussions": true, + "components": true, + "tutorials": true, + "showcase": true, + "jobs": true, + "collaboration": { + "enabled": true, + "publicSessions": true, + "privateSessions": true, + "maxSessionSize": 10, + "audioEnabled": true, + "videoEnabled": false, + "screenShareEnabled": true, + "requireAuth": true + }, + "notifications": { + "enabled": true, + "channels": ["discussions", "sessions", "components", "invites"], + "persistDays": 30 + } + }, + "branding": { + "primaryColor": "#FF6B6B", + "secondaryColor": "#4ECDC4", + "logo": "https://opennoodl.org/community-logo.png", + "favicon": "https://opennoodl.org/favicon.ico", + "customCSS": null + }, + "moderation": { + "moderators": ["community-admin"], + "requireApproval": { + "components": false, + "tutorials": true, + "showcaseProjects": true, + "jobs": false + }, + "autoModerationEnabled": true, + "bannedUsers": [] + }, + "integrations": { + "github": { + "org": "The-Low-Code-Foundation", + "discussionsRepo": "The-Low-Code-Foundation/opennoodl-community", + "issuesRepo": "The-Low-Code-Foundation/OpenNoodl" + }, + "discord": { + "enabled": false, + "webhookUrl": null, + "serverId": null + }, + "slack": { + "enabled": false, + "webhookUrl": null + } + }, + "limits": { + "maxComponentsPerUser": 50, + "maxSessionsPerUser": 5, + "maxInvitesPerSession": 20, + "rateLimit": { + "sessionsPerHour": 10, + "invitesPerHour": 50 + } + } +} +``` + +### 3. GitHub Actions Workflows + +See `GIT-5-to-GIT-11-Live-Collaboration-Community-System.md` for complete workflow implementations. + +### 4. Editor Integration - CommunityManager Service + +**File: `packages/noodl-editor/src/editor/src/services/CommunityManager.ts`** + +Key responsibilities: + +- Load and validate community configurations from GitHub repos +- Manage multiple community memberships +- Background sync of community configs +- Server health monitoring +- Community switching + +--- + +## Implementation Tasks + +- [ ] Create `opennoodl-community` template repository with full structure +- [ ] Implement `community.json` schema and JSON Schema validation +- [ ] Create GitHub Actions workflows for validation and sync +- [ ] Implement `CommunityManager` service in editor +- [ ] Create community settings UI panel +- [ ] Add "Add Community" flow (paste GitHub URL) +- [ ] Add "Create Community" flow (fork template) +- [ ] Implement background sync for community configs +- [ ] Add server health monitoring +- [ ] Create community switcher UI component + +--- + +## Verification Steps + +- [ ] Can fork `opennoodl-community` template and customize `community.json` +- [ ] GitHub Actions validate configuration on push +- [ ] Editor can add community by pasting GitHub URL +- [ ] Editor validates community config and checks server health +- [ ] Can switch between multiple communities +- [ ] Background sync updates community configs +- [ ] Invalid communities show error states +- [ ] Can remove communities (except official) + +--- + +## Files to Create + +``` +packages/noodl-editor/src/editor/src/services/ +├── CommunityManager.ts # Community management service +├── CommunityTypes.ts # TypeScript interfaces +└── CommunityValidation.ts # Schema validation + +packages/noodl-editor/src/editor/src/views/panels/CommunityPanel/ +├── CommunitySettings.tsx # Settings UI +├── CommunitySettings.module.scss +├── components/ +│ ├── CommunitySwitcher.tsx # Dropdown to switch communities +│ ├── AddCommunityDialog.tsx # Add by URL dialog +│ └── CommunityHealthIndicator.tsx # Server health display +└── hooks/ + └── useCommunityManager.ts # React hook for manager +``` + +--- + +## Notes + +- Official OpenNoodl community cannot be removed (hardcoded) +- Community configs are cached locally for offline access +- Server health checks run every sync cycle (15 minutes) +- Failed health checks show warning but don't remove community + +--- + +## Related Tasks + +- **GIT-006**: Server Infrastructure (signaling, sync, notifications servers) +- **GIT-007**: WebRTC Collaboration Client +- **GIT-009**: Community Tab UI/UX diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-006-server-infrastructure/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-006-server-infrastructure/README.md new file mode 100644 index 0000000..e682292 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-006-server-infrastructure/README.md @@ -0,0 +1,330 @@ +# GIT-006: Server Infrastructure (Signaling, Sync, Notifications) + +## Overview + +**Priority:** Critical +**Estimated Hours:** 80-100 +**Dependencies:** GIT-005 (Community Infrastructure) +**Status:** 🔴 Not Started + +Build the three core server components required for live collaboration and notifications: + +1. **Signaling Server** - WebRTC peer discovery +2. **Sync Server** - WebSocket fallback for CRDT sync +3. **Notification Server** - Persistent cross-session notifications + +--- + +## Strategic Context + +These servers are the backbone of the collaboration system. They're designed to be: + +- **Stateless** - Horizontally scalable +- **Self-hostable** - Community operators can run their own +- **Lightweight** - Minimal dependencies, easy to deploy +- **Observable** - Health checks, metrics, logging + +--- + +## Technical Requirements + +### 1. Signaling Server (WebRTC Peer Discovery) + +Minimal WebSocket server that helps peers find each other for WebRTC connections. **No project data passes through this server.** + +**Repository:** `opennoodl-signaling-server` + +**Key Features:** + +- Room-based peer management +- WebRTC signal relay (SDP, ICE candidates) +- Heartbeat for dead connection detection +- Invite forwarding for online users +- Prometheus-compatible metrics + +**Protocol Messages:** + +| Message Type | Direction | Description | +| ------------- | ---------------- | --------------------------- | +| `join` | Client → Server | Join a collaboration room | +| `joined` | Server → Client | Confirmation with peer list | +| `peer-joined` | Server → Clients | New peer joined room | +| `peer-left` | Server → Clients | Peer left room | +| `signal` | Bidirectional | WebRTC signaling data | +| `invite` | Client → Server | Invite user to session | +| `error` | Server → Client | Error notification | + +**Environment Variables:** + +| Variable | Default | Description | +| -------------------- | ------- | ----------------------- | +| `PORT` | 4444 | Server port | +| `MAX_ROOM_SIZE` | 20 | Maximum peers per room | +| `HEARTBEAT_INTERVAL` | 30000 | Heartbeat interval (ms) | + +**Endpoints:** + +| Endpoint | Method | Description | +| ---------- | --------- | --------------------------- | +| `/health` | GET | Health check (returns JSON) | +| `/metrics` | GET | Prometheus metrics | +| `/` | WebSocket | Signaling connection | + +--- + +### 2. Sync Server (WebSocket Fallback) + +Traditional WebSocket server using Yjs for CRDT synchronization. Used when WebRTC peer-to-peer fails. + +**Repository:** `opennoodl-sync-server` + +**Key Features:** + +- Yjs WebSocket provider compatibility +- Optional LevelDB persistence +- Automatic garbage collection +- Document isolation by room + +**Environment Variables:** + +| Variable | Default | Description | +| -------------------- | -------- | -------------------------- | +| `PORT` | 4445 | Server port | +| `PERSIST_DIR` | `./data` | Persistence directory | +| `ENABLE_PERSISTENCE` | `true` | Enable LevelDB persistence | + +**Dependencies:** + +- `yjs` - CRDT library +- `y-websocket` - WebSocket provider +- `y-leveldb` - Optional persistence + +--- + +### 3. Notification Server (Persistent Notifications) + +Server for managing cross-session notifications (invites, community events, mentions). + +**Repository:** `opennoodl-notification-server` + +**Key Features:** + +- WebSocket-based real-time delivery +- Persistent storage (survives server restart) +- Automatic TTL-based cleanup +- Multi-device support (same user on multiple devices) +- Notification types: invite, mention, thread, session, component, system + +**Protocol Messages:** + +| Message Type | Direction | Description | +| --------------------- | --------------- | ------------------------- | +| `authenticate` | Client → Server | Authenticate with user ID | +| `authenticated` | Server → Client | Auth confirmation | +| `get-notifications` | Client → Server | Request notification list | +| `notifications` | Server → Client | List of notifications | +| `notification` | Server → Client | New notification | +| `mark-read` | Client → Server | Mark notification as read | +| `delete-notification` | Client → Server | Delete a notification | +| `send-notification` | Client → Server | Send to another user | + +**Environment Variables:** + +| Variable | Default | Description | +| ---------- | ---------------------- | -------------------------- | +| `PORT` | 4446 | Server port | +| `DB_FILE` | `./notifications.json` | Database file path | +| `TTL_DAYS` | 30 | Notification expiry (days) | + +--- + +## Deployment Options + +### Docker Compose (Recommended for Self-Hosting) + +```yaml +version: '3.8' + +services: + signaling: + build: ./opennoodl-signaling-server + ports: + - '4444:4444' + environment: + - PORT=4444 + - MAX_ROOM_SIZE=20 + restart: unless-stopped + healthcheck: + test: ['CMD', 'wget', '--quiet', '--tries=1', '--spider', 'http://localhost:4444/health'] + interval: 30s + timeout: 10s + retries: 3 + + sync: + build: ./opennoodl-sync-server + ports: + - '4445:4445' + environment: + - PORT=4445 + - ENABLE_PERSISTENCE=true + - PERSIST_DIR=/data + volumes: + - sync-data:/data + restart: unless-stopped + + notifications: + build: ./opennoodl-notification-server + ports: + - '4446:4446' + environment: + - PORT=4446 + - DB_FILE=/data/notifications.json + - TTL_DAYS=30 + volumes: + - notification-data:/data + restart: unless-stopped + +volumes: + sync-data: + notification-data: +``` + +### One-Click Deploy Options + +Each server includes configuration for: + +- **Railway** (`railway.json`) +- **Render** (`render.yaml`) +- **Fly.io** (`fly.toml`) + +--- + +## Implementation Tasks + +### Signaling Server + +- [ ] Create `opennoodl-signaling-server` repository +- [ ] Implement WebSocket server with room management +- [ ] Implement peer join/leave handling +- [ ] Implement signal relay +- [ ] Implement invite forwarding +- [ ] Add heartbeat for dead connection detection +- [ ] Add health check endpoint +- [ ] Add Prometheus metrics endpoint +- [ ] Create Dockerfile +- [ ] Create one-click deploy configs +- [ ] Write deployment documentation + +### Sync Server + +- [ ] Create `opennoodl-sync-server` repository +- [ ] Implement Yjs WebSocket provider integration +- [ ] Add optional LevelDB persistence +- [ ] Add health check endpoint +- [ ] Create Dockerfile +- [ ] Create one-click deploy configs + +### Notification Server + +- [ ] Create `opennoodl-notification-server` repository +- [ ] Implement WebSocket authentication +- [ ] Implement notification storage (LowDB initially) +- [ ] Implement notification delivery +- [ ] Implement mark-read/delete operations +- [ ] Implement TTL-based cleanup +- [ ] Add multi-device support +- [ ] Add health check endpoint +- [ ] Create Dockerfile +- [ ] Create one-click deploy configs + +### Deployment + +- [ ] Deploy official signaling server +- [ ] Deploy official sync server +- [ ] Deploy official notification server +- [ ] Configure SSL certificates +- [ ] Set up monitoring (Grafana/Prometheus) +- [ ] Set up alerting +- [ ] Load testing + +--- + +## Verification Steps + +- [ ] Signaling server helps peers find each other +- [ ] Sync server synchronizes Yjs documents +- [ ] Notification server stores and delivers notifications +- [ ] Health endpoints return 200 OK +- [ ] Metrics endpoints expose data +- [ ] Docker Compose brings up all services +- [ ] One-click deploy works on Railway/Render +- [ ] Servers handle connection failures gracefully +- [ ] Old notifications are cleaned up automatically +- [ ] Servers restart automatically after crash + +--- + +## Security Considerations + +1. **No sensitive data in signaling** - Only relay WebRTC signals, no content +2. **Rate limiting** - Prevent abuse of all servers +3. **Authentication** - Notification server requires user auth +4. **CORS** - Properly configured for cross-origin requests +5. **WSS only** - Require secure WebSocket connections in production + +--- + +## Scaling Notes + +| Server | Bottleneck | Scaling Strategy | +| ------------- | --------------------- | ----------------------------------- | +| Signaling | Memory (room state) | Horizontal with sticky sessions | +| Sync | Storage (persistence) | Shard by room prefix | +| Notifications | Storage | Replace LowDB with Redis/PostgreSQL | + +--- + +## Files to Create + +``` +External repositories (separate from OpenNoodl): + +opennoodl-signaling-server/ +├── index.js +├── package.json +├── Dockerfile +├── railway.json +├── render.yaml +├── fly.toml +└── README.md + +opennoodl-sync-server/ +├── index.js +├── package.json +├── Dockerfile +└── README.md + +opennoodl-notification-server/ +├── index.js +├── package.json +├── Dockerfile +└── README.md +``` + +--- + +## Notes + +- All servers designed to be stateless (horizontally scalable) +- Sync server can optionally persist data with LevelDB +- Notification server uses LowDB initially (can upgrade to Redis/PostgreSQL for scale) +- All servers include CORS headers for cross-origin requests +- WebSocket connections include heartbeat/ping-pong for dead connection detection + +--- + +## Related Tasks + +- **GIT-005**: Community Infrastructure (defines server URLs in community.json) +- **GIT-007**: WebRTC Collaboration Client (consumes these servers) +- **GIT-008**: Notification System (client for notification server) diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-007-webrtc-collaboration/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-007-webrtc-collaboration/README.md new file mode 100644 index 0000000..15667ac --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-007-webrtc-collaboration/README.md @@ -0,0 +1,342 @@ +# GIT-007: WebRTC Collaboration Client + +## Overview + +**Priority:** Critical +**Estimated Hours:** 100-130 +**Dependencies:** GIT-005, GIT-006 +**Status:** 🔴 Not Started + +Implement the client-side WebRTC collaboration system that enables real-time multi-user editing, cursor sharing, audio/video chat, and seamless fallback to WebSocket sync. + +This is the **core collaboration feature** - enabling "Google Docs for visual programming." + +--- + +## Strategic Context + +This task delivers the most visible and impactful feature of the collaboration system: + +- **Real-time multi-user editing** - See changes as they happen +- **Presence awareness** - Cursors, selections, viewports of all participants +- **Audio/video chat** - Built-in communication (no need for separate apps) +- **Peer-to-peer** - Low latency, no server relay for data +- **Automatic fallback** - Falls back to WebSocket when P2P fails + +--- + +## Technical Requirements + +### 1. CollaborationManager Service + +**File:** `packages/noodl-editor/src/editor/src/services/CollaborationManager.ts` + +Central service managing all collaboration functionality: + +```typescript +interface CollaborationSession { + id: string; + roomId: string; + projectId: string; + isHost: boolean; + isPublic: boolean; + title: string; + description?: string; + maxParticipants: number; + participants: Map; + createdAt: Date; +} + +interface Participant { + peerId: string; + userId: string; + name: string; + avatar?: string; + color: string; + cursor?: { x: number; y: number }; + selection?: { nodeId: string }; + viewport?: { x: number; y: number; zoom: number }; + audio: { enabled: boolean; stream?: MediaStream }; + video: { enabled: boolean; stream?: MediaStream }; + isHost: boolean; + joinedAt: Date; +} +``` + +**Key Methods:** + +| Method | Description | +| ---------------------------- | ---------------------------------------- | +| `startSession(options)` | Start a new collaboration session (host) | +| `joinSession(roomId)` | Join an existing session | +| `leaveSession()` | Leave current session | +| `inviteUser(userId)` | Invite a user to current session | +| `updateCursor(x, y)` | Broadcast cursor position | +| `updateSelection(nodeId)` | Broadcast node selection | +| `updateViewport(x, y, zoom)` | Broadcast viewport state | +| `toggleAudio(enabled?)` | Toggle local audio | +| `toggleVideo(enabled?)` | Toggle local video | + +### 2. WebRTC Connection Flow + +``` +┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ +│ Peer A │ │ Signaling Server │ │ Peer B │ +└──────┬───────┘ └────────┬─────────┘ └──────┬───────┘ + │ │ │ + │ 1. join(room) │ │ + │─────────────────────>│ │ + │ │ │ + │ 2. joined(peers) │ │ + │<─────────────────────│ │ + │ │ │ + │ │ 3. join(room) │ + │ │<─────────────────────│ + │ │ │ + │ │ 4. peer-joined │ + │<─────────────────────│─────────────────────>│ + │ │ │ + │ 5. signal(offer) │ │ + │─────────────────────>│─────────────────────>│ + │ │ │ + │ │ 6. signal(answer) │ + │<─────────────────────│<─────────────────────│ + │ │ │ + │ 7. P2P Connection Established │ + │<═══════════════════════════════════════════>│ +``` + +### 3. Yjs Integration (CRDT Sync) + +Use Yjs for conflict-free replicated data types: + +```typescript +// Document structure +const doc = new Y.Doc(); +const yNodes = doc.getArray('nodes'); +const yConnections = doc.getArray('connections'); +const yProperties = doc.getMap('properties'); + +// Awareness (cursors, selections - not persisted) +const awareness = provider.awareness; +awareness.setLocalState({ + user: { name, color, avatar }, + cursor: { x, y }, + selection: { nodeId } +}); +``` + +### 4. Connection Fallback Strategy + +``` +1. Try WebRTC P2P (y-webrtc) + ↓ (if fails after 5 seconds) +2. Try WebSocket via Sync Server (y-websocket) + ↓ (if fails) +3. Show error, allow retry +``` + +### 5. Media Handling (Audio/Video) + +```typescript +// Initialize local media +const stream = await navigator.mediaDevices.getUserMedia({ + audio: { echoCancellation: true, noiseSuppression: true }, + video: { width: 1280, height: 720 } +}); + +// Attach to peer connection +peer.addStream(stream); + +// Handle remote streams +peer.on('stream', (remoteStream) => { + // Display in participant's video element +}); +``` + +--- + +## User Flows + +### Starting a Session (Host) + +1. Click "Start Collaboration" in editor toolbar +2. Fill in session details (title, description, public/private) +3. Configure options (audio, video, max participants) +4. Session starts, room ID generated +5. Copy link or invite users directly + +### Joining a Session (Guest) + +1. Receive invitation notification OR click session link +2. Preview session details +3. Configure join options (audio, video) +4. Click "Join" +5. WebRTC connection established +6. Project state synced + +### During Session + +- See other participants' cursors (colored) +- See other participants' selections (highlighted) +- See avatar thumbnails of participants +- Optionally see video feeds +- Changes to nodes sync in real-time +- Can toggle audio/video anytime + +### Leaving a Session + +1. Click "Leave Session" +2. Confirm if host (session continues or ends) +3. Cleanup: stop media, close connections +4. Return to solo editing mode + +--- + +## Implementation Tasks + +### Phase 1: Core Service (20-25 hours) + +- [ ] Create `CollaborationManager.ts` service +- [ ] Implement session creation (host) +- [ ] Implement session joining (guest) +- [ ] Implement signaling server communication +- [ ] Implement WebRTC peer connections +- [ ] Implement graceful disconnection + +### Phase 2: Yjs Integration (20-25 hours) + +- [ ] Set up Yjs document structure +- [ ] Implement WebRTC provider (y-webrtc) +- [ ] Implement WebSocket fallback (y-websocket) +- [ ] Sync project nodes to Yjs +- [ ] Sync project connections to Yjs +- [ ] Handle remote changes in ProjectModel +- [ ] Implement conflict resolution UI + +### Phase 3: Awareness (15-20 hours) + +- [ ] Implement cursor position broadcasting +- [ ] Implement selection broadcasting +- [ ] Implement viewport broadcasting +- [ ] Create remote cursor renderer +- [ ] Create remote selection highlighter +- [ ] Add participant list UI + +### Phase 4: Media (20-25 hours) + +- [ ] Implement local audio capture +- [ ] Implement local video capture +- [ ] Implement audio/video toggle +- [ ] Display remote audio (spatial if possible) +- [ ] Display remote video feeds +- [ ] Handle media permission errors + +### Phase 5: UI Components (15-20 hours) + +- [ ] Create collaboration toolbar +- [ ] Create start session dialog +- [ ] Create join session dialog +- [ ] Create participant list panel +- [ ] Create video grid component +- [ ] Create session info panel + +### Phase 6: Integration (10-15 hours) + +- [ ] Integrate with existing editor +- [ ] Add collaboration indicators to canvas +- [ ] Handle undo/redo in collaboration mode +- [ ] Test with multiple participants +- [ ] Performance optimization + +--- + +## Verification Steps + +- [ ] Can start a collaboration session +- [ ] Can join a session via room ID +- [ ] WebRTC peers connect automatically +- [ ] Cursor positions sync in real-time +- [ ] Node changes sync across all peers +- [ ] Selections visible to all participants +- [ ] Audio chat works between peers +- [ ] Video feeds display correctly +- [ ] Falls back to WebSocket when WebRTC fails +- [ ] Can toggle audio/video during session +- [ ] Disconnections handled gracefully +- [ ] Session persists if host leaves temporarily +- [ ] Can invite users to session + +--- + +## Files to Create + +``` +packages/noodl-editor/src/editor/src/services/ +├── CollaborationManager.ts # Main collaboration service +├── CollaborationTypes.ts # TypeScript interfaces +├── CollaborationYjsAdapter.ts # Yjs <-> ProjectModel adapter +└── CollaborationMediaManager.ts # Audio/video handling + +packages/noodl-editor/src/editor/src/views/ +├── CollaborationToolbar/ +│ ├── CollaborationToolbar.tsx +│ ├── CollaborationToolbar.module.scss +│ ├── StartSessionDialog.tsx +│ ├── JoinSessionDialog.tsx +│ └── SessionInfoPanel.tsx +├── CollaborationOverlay/ +│ ├── RemoteCursors.tsx # Render remote cursors on canvas +│ ├── RemoteSelections.tsx # Render remote selections +│ └── ParticipantAvatars.tsx # Show participant list +└── CollaborationVideo/ + ├── VideoGrid.tsx # Video feed layout + └── LocalVideo.tsx # Self-view + +packages/noodl-editor/src/editor/src/hooks/ +├── useCollaboration.ts # Hook for collaboration state +├── useRemoteCursors.ts # Hook for cursor rendering +└── useMediaStream.ts # Hook for audio/video +``` + +--- + +## Dependencies to Add + +```json +{ + "yjs": "^13.6.0", + "y-webrtc": "^10.2.0", + "y-websocket": "^1.5.0", + "simple-peer": "^9.11.0", + "lib0": "^0.2.85" +} +``` + +--- + +## Performance Considerations + +1. **Cursor throttling** - Don't broadcast every mouse move (throttle to 50ms) +2. **Viewport culling** - Don't render cursors outside visible area +3. **Yjs updates** - Batch updates where possible +4. **Media quality** - Adaptive bitrate based on network conditions +5. **Connection limits** - Max 10-20 participants for performance + +--- + +## Security Notes + +1. **Room IDs** - Use UUIDs, hard to guess +2. **Signaling** - Only relay WebRTC signals, no content +3. **Media** - Encrypted via SRTP (built into WebRTC) +4. **Project data** - Synced P2P, not through server (except fallback) + +--- + +## Related Tasks + +- **GIT-005**: Community Infrastructure (provides server URLs) +- **GIT-006**: Server Infrastructure (signaling, sync, notifications) +- **GIT-008**: Notification System (for session invites) +- **GIT-010**: Session Discovery (for finding public sessions) diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-008-notification-system/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-008-notification-system/README.md new file mode 100644 index 0000000..ca238f1 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-008-notification-system/README.md @@ -0,0 +1,326 @@ +# GIT-008: Notification System + +## Overview + +**Priority:** High +**Estimated Hours:** 50-70 +**Dependencies:** GIT-006 (Notification Server) +**Status:** 🔴 Not Started + +Implement persistent cross-session notification system for collaboration invites, community events, mentions, and updates. + +--- + +## Strategic Context + +Notifications bridge sessions - they ensure users don't miss: + +- **Collaboration invites** - Someone wants to work with you +- **Mentions** - Someone referenced you in a discussion +- **Session updates** - Public session started by someone you follow +- **Component updates** - New component published in your community +- **System messages** - Important announcements + +--- + +## Technical Requirements + +### 1. NotificationManager Service + +**File:** `packages/noodl-editor/src/editor/src/services/NotificationManager.ts` + +```typescript +interface Notification { + id: string; + userId: string; + fromUserId?: string; + type: 'invite' | 'mention' | 'thread' | 'session' | 'component' | 'system'; + title: string; + message: string; + data?: Record; + read: boolean; + createdAt: Date; + expiresAt: Date; + readAt?: Date; + actions?: NotificationAction[]; +} + +interface NotificationAction { + label: string; + action: string; + primary?: boolean; +} +``` + +**Key Methods:** + +| Method | Description | +| -------------------------------------- | ------------------------------ | +| `initialize(userId)` | Connect to notification server | +| `getNotifications()` | Get all notifications | +| `getUnreadCount()` | Get unread count | +| `markAsRead(id)` | Mark notification as read | +| `deleteNotification(id)` | Delete a notification | +| `sendNotification(userId, type, data)` | Send to another user | +| `destroy()` | Disconnect and cleanup | + +### 2. Notification Types + +| Type | Trigger | Actions | +| ----------- | ---------------------------------- | -------------- | +| `invite` | Someone invites you to collaborate | Join, Decline | +| `mention` | Someone mentions you in discussion | View Thread | +| `thread` | New reply in discussion you follow | View Thread | +| `session` | Public session started | Join Session | +| `component` | New component in your community | View Component | +| `system` | System announcement | Dismiss | + +### 3. Toast Notifications + +Real-time toasts for new notifications: + +```typescript +// Toast component structure +interface ToastProps { + notification: Notification; + onAction: (action: string) => void; + onDismiss: () => void; +} + +// Toast behavior +- Appears bottom-right of editor +- Auto-dismisses after 10 seconds +- Can be clicked for quick action +- Can be swiped to dismiss +- Queue system for multiple notifications +``` + +### 4. Notification Center + +Panel/popover for viewing all notifications: + +``` +┌─────────────────────────────────┐ +│ Notifications ✕ │ +├─────────────────────────────────┤ +│ ┌─────────────────────────────┐ │ +│ │ 👥 Collaboration Invite │ │ +│ │ Alice invited you to │ │ +│ │ "Building a Dashboard" │ │ +│ │ [Join] [Decline] 2m ago│ │ +│ └─────────────────────────────┘ │ +│ ┌─────────────────────────────┐ │ +│ │ 📦 New Component │ │ +│ │ Bob published "DataGrid" │ │ +│ │ [View] 1h ago│ │ +│ └─────────────────────────────┘ │ +│ ┌─────────────────────────────┐ │ +│ │ 💬 Discussion Reply │ │ +│ │ Charlie replied to your │ │ +│ │ question about routing │ │ +│ │ [View Thread] 3h ago│ │ +│ └─────────────────────────────┘ │ +├─────────────────────────────────┤ +│ [Mark All Read] [Clear All] │ +└─────────────────────────────────┘ +``` + +### 5. Desktop Notifications + +Leverage Electron's native notification API: + +```typescript +// Show native notification +new Notification('Collaboration Invite', { + body: 'Alice invited you to collaborate', + icon: '/path/to/icon.png', + tag: notification.id, // Prevents duplicates + requireInteraction: notification.type === 'invite' // Stay until clicked +}); + +notification.onclick = () => { + // Focus app and handle action +}; +``` + +--- + +## User Flows + +### Receiving Notification (Online) + +1. Notification arrives via WebSocket +2. Toast appears in editor +3. Badge updates on notification bell +4. Desktop notification shows (if enabled) +5. User can act on toast or dismiss + +### Receiving Notification (Offline) + +1. Notification stored on server +2. User opens app +3. Notifications fetched on connect +4. Multiple notifications batch-displayed +5. Badge shows unread count + +### Handling Notification Actions + +**Invite:** + +1. Click "Join" on invite notification +2. Session preview dialog opens +3. Confirm join options +4. Connect to collaboration session + +**Mention/Thread:** + +1. Click "View Thread" +2. Opens discussion in browser (GitHub Discussions) + +**Component:** + +1. Click "View Component" +2. Opens component in community panel + +--- + +## Implementation Tasks + +### Phase 1: Core Service (15-20 hours) + +- [ ] Create `NotificationManager.ts` service +- [ ] Implement WebSocket connection to notification server +- [ ] Implement authentication flow +- [ ] Implement notification fetching +- [ ] Implement notification parsing/typing +- [ ] Implement mark as read +- [ ] Implement delete notification +- [ ] Implement send notification +- [ ] Add reconnection logic +- [ ] Add offline support (local storage cache) + +### Phase 2: Toast UI (10-15 hours) + +- [ ] Create `NotificationToast.tsx` component +- [ ] Create toast container/queue +- [ ] Implement auto-dismiss +- [ ] Implement action buttons +- [ ] Implement swipe-to-dismiss +- [ ] Add animations +- [ ] Style according to notification type + +### Phase 3: Notification Center (15-20 hours) + +- [ ] Create `NotificationCenter.tsx` component +- [ ] Create `NotificationItem.tsx` component +- [ ] Implement notification list with grouping +- [ ] Implement "Mark All Read" +- [ ] Implement "Clear All" +- [ ] Add notification badge to header +- [ ] Implement search/filter (optional) + +### Phase 4: Desktop Notifications (5-10 hours) + +- [ ] Implement Electron notification API +- [ ] Add notification settings (enable/disable) +- [ ] Handle notification click → focus app +- [ ] Add notification sound (optional) +- [ ] Test on Windows, Mac, Linux + +### Phase 5: Integration (5-10 hours) + +- [ ] Connect to existing authentication +- [ ] Integrate with collaboration system +- [ ] Add to editor layout +- [ ] Test end-to-end flows +- [ ] Performance optimization + +--- + +## Verification Steps + +- [ ] Notifications persist across sessions +- [ ] Toast appears when notification received +- [ ] Can mark notifications as read +- [ ] Can delete notifications +- [ ] Unread count displays correctly +- [ ] Actions trigger correct behavior +- [ ] Desktop notifications work (Electron) +- [ ] Reconnects after connection loss +- [ ] Offline notifications cached and shown on reconnect +- [ ] Expired notifications cleaned up + +--- + +## Files to Create + +``` +packages/noodl-editor/src/editor/src/services/ +├── NotificationManager.ts # Main notification service +├── NotificationTypes.ts # TypeScript interfaces + +packages/noodl-editor/src/editor/src/views/ +├── NotificationCenter/ +│ ├── NotificationCenter.tsx # Main panel/popover +│ ├── NotificationCenter.module.scss +│ ├── NotificationItem.tsx # Individual notification +│ ├── NotificationBadge.tsx # Unread count badge +│ └── NotificationEmpty.tsx # Empty state +├── NotificationToast/ +│ ├── NotificationToast.tsx # Toast component +│ ├── NotificationToast.module.scss +│ └── ToastContainer.tsx # Toast queue manager + +packages/noodl-editor/src/editor/src/hooks/ +├── useNotifications.ts # Hook for notification state +└── useNotificationActions.ts # Hook for notification actions +``` + +--- + +## Notification Message Format + +```typescript +// Incoming notification from server +interface ServerNotification { + id: string; + userId: string; + fromUserId?: string; + type: 'invite' | 'mention' | 'thread' | 'session' | 'component' | 'system'; + data: { + // Type-specific data + sessionId?: string; + sessionTitle?: string; + threadUrl?: string; + componentId?: string; + componentName?: string; + message?: string; + [key: string]: unknown; + }; + read: boolean; + createdAt: string; // ISO date + expiresAt: string; // ISO date +} +``` + +--- + +## Settings + +| Setting | Default | Description | +| -------------------------- | ------- | ---------------------------- | +| `notifications.enabled` | `true` | Enable notifications | +| `notifications.desktop` | `true` | Enable desktop notifications | +| `notifications.sound` | `true` | Play sound on notification | +| `notifications.invites` | `true` | Show collaboration invites | +| `notifications.mentions` | `true` | Show mentions | +| `notifications.sessions` | `true` | Show public sessions | +| `notifications.components` | `true` | Show component updates | + +--- + +## Related Tasks + +- **GIT-006**: Server Infrastructure (notification server) +- **GIT-007**: WebRTC Collaboration Client (sends invites) +- **GIT-009**: Community Tab UI (displays community notifications) diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-009-community-ui/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-009-community-ui/README.md new file mode 100644 index 0000000..48b8140 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-009-community-ui/README.md @@ -0,0 +1,303 @@ +# GIT-009: Community Tab UI/UX + +## Overview + +**Priority:** High +**Estimated Hours:** 80-100 +**Dependencies:** GIT-005, GIT-007, GIT-008 +**Status:** 🔴 Not Started + +Create the Community tab UI that serves as the central hub for all community features: browsing communities, discovering sessions, exploring components, viewing tutorials, and accessing discussions. + +--- + +## Strategic Context + +The Community tab is the social layer of OpenNoodl. It surfaces: + +- **Live collaboration sessions** - Join others in real-time +- **Shared components** - Discover and install components +- **Learning resources** - Tutorials organized by skill level +- **Discussions** - Community Q&A via GitHub Discussions +- **Job board** - Opportunities in the ecosystem + +--- + +## UI Layout + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Community Switcher: [OpenNoodl Official ▾] [+ Add] │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────────┐│ +│ │ 🏠 Home │ 👥 Sessions │ 📦 Components │ 🎓 Learn │ 💬 Discuss │ 💼 Jobs │ │ +│ └─────────────────────────────────────────────────────────┘│ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────┐│ +│ │ [Tab Content] ││ +│ │ ││ +│ │ ││ +│ │ ││ +│ │ ││ +│ │ ││ +│ │ ││ +│ └─────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Tab Views + +### 1. Home View + +Featured content dashboard with quick actions: + +- **Live Now** - Active public sessions +- **Featured This Week** - Curated components +- **New Tutorials** - Recently published +- **Hot Discussions** - Trending threads +- **Quick Actions** - Start session, share component, ask question + +### 2. Sessions View + +Browse and join collaboration sessions: + +- **Filters**: Live now, scheduled, all +- **Session cards** with: + - Host avatar and name + - Session title and description + - Participant count / max + - Audio/video indicators + - Duration +- **Create Session** button +- **Join via Link** button + +### 3. Components View + +Browse shared component library: + +- **Search bar** with filters +- **Category filter** dropdown +- **Sort by**: Popular, recent, name +- **Component cards** with: + - Preview image or icon + - Name and description + - Author + - Stars and downloads + - Tags + - "Add to Library" button + +### 4. Learn View + +Tutorial browser organized by skill level: + +- **Learning paths** - Curated sequences +- **Level filter**: Beginner, intermediate, advanced +- **Format filter**: Video, article, interactive +- **Tutorial cards** with: + - Thumbnail + - Title and description + - Author + - Duration + - Level badge + +### 5. Discuss View + +GitHub Discussions integration: + +- **Category tabs**: All, Q&A, Show and Tell, Ideas, General +- **Sort**: Recent, top, unanswered +- **Discussion items** with: + - Title and preview + - Author + - Category + - Comment and reaction counts + - Time ago +- **Start Discussion** button (opens GitHub) + +### 6. Jobs View + +Community job board: + +- **Filters**: Type (full-time, contract, freelance), remote, experience +- **Job cards** with: + - Company logo + - Job title and company + - Description + - Tags (type, location, experience) + - Salary (if provided) + - Posted date + - "Apply" button + +--- + +## Implementation Tasks + +### Phase 1: Panel Structure (15-20 hours) + +- [ ] Create `CommunityPanel/CommunityPanel.tsx` +- [ ] Create tab navigation component +- [ ] Create community switcher component +- [ ] Integrate with CommunityManager +- [ ] Add to editor sidebar +- [ ] Create panel layout and styles + +### Phase 2: Home View (15-20 hours) + +- [ ] Create `HomeView.tsx` +- [ ] Implement welcome banner with community branding +- [ ] Implement "Live Now" section +- [ ] Implement "Featured" section +- [ ] Implement "New Tutorials" section +- [ ] Implement "Hot Discussions" section +- [ ] Create quick action buttons +- [ ] Fetch featured content from community repo + +### Phase 3: Sessions View (15-20 hours) + +- [ ] Create `SessionsView.tsx` +- [ ] Implement session list with cards +- [ ] Implement session filters +- [ ] Create "Create Session" dialog +- [ ] Create "Join via Link" dialog +- [ ] Integrate with CollaborationManager +- [ ] Implement real-time session count updates + +### Phase 4: Components View (15-20 hours) + +- [ ] Create `ComponentsView.tsx` +- [ ] Implement search with debounce +- [ ] Implement category filter +- [ ] Implement sort options +- [ ] Create component cards +- [ ] Implement "Add to Library" action +- [ ] Create component preview modal +- [ ] Fetch from community components registry + +### Phase 5: Learn View (10-15 hours) + +- [ ] Create `LearnView.tsx` +- [ ] Implement learning paths section +- [ ] Implement level filter +- [ ] Implement format filter +- [ ] Create tutorial cards +- [ ] Fetch from community tutorials directory + +### Phase 6: Discuss View (8-12 hours) + +- [ ] Create `DiscussView.tsx` +- [ ] Implement category tabs +- [ ] Implement sort options +- [ ] Create discussion item component +- [ ] Integrate GitHub Discussions API via Octokit +- [ ] Implement "Start Discussion" action + +### Phase 7: Jobs View (8-12 hours) + +- [ ] Create `JobsView.tsx` +- [ ] Implement job filters +- [ ] Create job cards +- [ ] Implement "Apply" action +- [ ] Fetch from community jobs listings + +### Phase 8: Polish (10-15 hours) + +- [ ] Add loading states and skeletons +- [ ] Add error handling and retry +- [ ] Implement infinite scroll +- [ ] Add keyboard navigation +- [ ] Add animations and transitions +- [ ] Responsive layout adjustments + +--- + +## Verification Steps + +- [ ] Community tab loads without errors +- [ ] Can switch between communities +- [ ] Home view shows featured content +- [ ] Can browse and join sessions +- [ ] Can search and install components +- [ ] Tutorials load and display correctly +- [ ] Discussions integrate with GitHub +- [ ] Job board displays listings +- [ ] All filters work correctly +- [ ] UI is responsive and performant +- [ ] Offline/error states handled gracefully + +--- + +## Files to Create + +``` +packages/noodl-editor/src/editor/src/views/panels/CommunityPanel/ +├── CommunityPanel.tsx +├── CommunityPanel.module.scss +├── components/ +│ ├── CommunityHeader.tsx +│ ├── CommunitySwitcher.tsx +│ ├── TabNavigation.tsx +│ ├── EmptyState.tsx +│ ├── SessionCard.tsx +│ ├── ComponentCard.tsx +│ ├── TutorialCard.tsx +│ ├── DiscussionItem.tsx +│ ├── JobCard.tsx +│ └── LoadingSkeletons.tsx +├── views/ +│ ├── HomeView.tsx +│ ├── SessionsView.tsx +│ ├── ComponentsView.tsx +│ ├── LearnView.tsx +│ ├── DiscussView.tsx +│ └── JobsView.tsx +├── dialogs/ +│ ├── CreateSessionDialog.tsx +│ ├── JoinSessionDialog.tsx +│ ├── ComponentPreviewModal.tsx +│ └── ShareComponentDialog.tsx +└── hooks/ + ├── useCommunityContent.ts + ├── useSessions.ts + ├── useComponents.ts + ├── useTutorials.ts + ├── useDiscussions.ts + └── useJobs.ts +``` + +--- + +## Data Fetching + +All content is fetched from the community's GitHub repository: + +| Content | Source | +| ----------- | ------------------------------------ | +| Featured | `featured.json` | +| Sessions | `collaboration/public-sessions.json` | +| Components | `components/registry.json` | +| Tutorials | `tutorials/{level}/index.json` | +| Discussions | GitHub Discussions API | +| Jobs | `jobs/listings.json` | + +--- + +## Styling Notes + +- Use CSS variables from theme for colors +- Follow Panel UI Style Guide +- Dark theme first +- Consistent card styling across views +- Smooth animations for transitions +- Loading skeletons match card dimensions + +--- + +## Related Tasks + +- **GIT-005**: Community Infrastructure (provides data sources) +- **GIT-007**: WebRTC Collaboration (sessions integration) +- **GIT-008**: Notification System (community notifications) +- **GIT-010**: Session Discovery (extends sessions view) diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-010-session-discovery/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-010-session-discovery/README.md new file mode 100644 index 0000000..11db7ac --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-010-session-discovery/README.md @@ -0,0 +1,357 @@ +# GIT-010: Session Discovery & Joining + +## Overview + +**Priority:** High +**Estimated Hours:** 50-70 +**Dependencies:** GIT-007, GIT-009 +**Status:** 🔴 Not Started + +Streamline the session discovery and joining experience with deep linking, quick join flows, session previews, and favorites. + +--- + +## Strategic Context + +This task focuses on reducing friction for joining collaboration sessions: + +- **Deep links** - Share `opennoodl://` links that open directly in the app +- **Quick join** - One-click joining from invites +- **Session preview** - See what you're joining before committing +- **History** - Remember past sessions for easy rejoin +- **Favorites** - Bookmark sessions you join frequently + +--- + +## Technical Requirements + +### 1. Deep Link Protocol + +Register `opennoodl://` protocol handler for session joining: + +``` +Examples: +opennoodl://session/abc123 +opennoodl://session/abc123?audio=true&video=false +opennoodl://community/add?repo=user/my-community +opennoodl://component/install?repo=user/repo&component=DataGrid +``` + +**Protocol Handlers:** + +| Path | Description | +| ------------------- | --------------------------- | +| `session/{roomId}` | Join collaboration session | +| `community/add` | Add community by repo URL | +| `component/install` | Install component from repo | + +### 2. DeepLinkHandler Service + +**File:** `packages/noodl-editor/src/editor/src/utils/DeepLinkHandler.ts` + +```typescript +class DeepLinkHandler { + // Register handlers for different paths + register(path: string, handler: (params: any) => void): void; + + // Parse and handle incoming URL + handleUrl(url: string): void; + + // Generate shareable link + generateLink(path: string, params?: Record): string; +} +``` + +**Electron Integration:** + +- Register protocol on app startup +- Handle `app.on('open-url')` for links when app is running +- Handle `process.argv` for links that launch the app + +### 3. Session Preview + +Before joining, show session details: + +``` +┌─────────────────────────────────────────┐ +│ Join Collaboration Session ✕ │ +├─────────────────────────────────────────┤ +│ │ +│ [Avatar] Alice is hosting │ +│ │ +│ 📌 Building a User Dashboard │ +│ Working on the main dashboard layout │ +│ and navigation components. │ +│ │ +│ ┌─────────┬─────────┬─────────┐ │ +│ │ 👥 4/10 │ 📁 Proj │ 🕐 45m │ │ +│ └─────────┴─────────┴─────────┘ │ +│ │ +│ Features: │ +│ 🎤 Audio Chat 🖥️ Screen Share │ +│ │ +│ Participants: │ +│ [A] [B] [C] [D] │ +│ │ +│ ┌────────────────────────────────┐ │ +│ │ ☑ Join with audio enabled │ │ +│ │ ☐ Join with video enabled │ │ +│ └────────────────────────────────┘ │ +│ │ +│ [Cancel] [Join Session] │ +└─────────────────────────────────────────┘ +``` + +### 4. Quick Join Widget + +Floating widget for incoming invites: + +``` +┌─────────────────────────────────────┐ +│ 👥 Collaboration Invite [−] [✕]│ +├─────────────────────────────────────┤ +│ [Avatar] Alice invited you │ +│ │ +│ "Building a User Dashboard" │ +│ │ +│ 👥 4 participants 🎤 Audio │ +│ │ +│ [Decline] [Join Now] │ +└─────────────────────────────────────┘ +``` + +### 5. Session History Manager + +**File:** `packages/noodl-editor/src/editor/src/services/SessionHistoryManager.ts` + +```typescript +interface SessionHistoryEntry { + sessionId: string; + roomId: string; + title: string; + host: { + userId: string; + name: string; + avatar?: string; + }; + joinedAt: Date; + leftAt?: Date; + duration: number; + isFavorite: boolean; +} + +class SessionHistoryManager { + // Add entry when joining session + addEntry(session: any): void; + + // Update entry when leaving + updateEntry(sessionId: string, updates: Partial): void; + + // Toggle favorite + toggleFavorite(sessionId: string): void; + + // Get all history + getHistory(): SessionHistoryEntry[]; + + // Get favorites only + getFavorites(): SessionHistoryEntry[]; +} +``` + +--- + +## User Flows + +### Joining via Deep Link + +1. User clicks `opennoodl://session/abc123` link +2. App opens (or focuses if already open) +3. Session preview dialog appears +4. User configures join options +5. Click "Join Session" +6. WebRTC connection established + +### Joining via Invitation + +1. Notification appears (toast + notification center) +2. Click "Join Now" on toast OR +3. Open notification center, click invitation +4. Session preview dialog appears +5. Configure and join + +### Joining from History + +1. Open "Recent Sessions" panel +2. See list of past sessions +3. Click session to preview +4. If session still active, join option shown +5. Join or view details + +### Managing Favorites + +1. During/after session, click star icon +2. Session added to favorites +3. Favorites show in dedicated section +4. Quick access for frequently joined sessions + +--- + +## Implementation Tasks + +### Phase 1: Deep Link Handler (10-15 hours) + +- [ ] Create `DeepLinkHandler.ts` service +- [ ] Implement protocol registration in Electron +- [ ] Handle `open-url` event +- [ ] Handle startup with URL argument +- [ ] Implement session handler +- [ ] Implement community handler +- [ ] Implement component handler +- [ ] Generate shareable links + +### Phase 2: Session Preview (10-15 hours) + +- [ ] Create `SessionPreview.tsx` dialog +- [ ] Fetch session info from signaling server +- [ ] Display host, participants, features +- [ ] Implement join options checkboxes +- [ ] Handle session not found +- [ ] Handle session full +- [ ] Implement "Join" action + +### Phase 3: Quick Join Widget (8-12 hours) + +- [ ] Create `QuickJoinWidget.tsx` component +- [ ] Implement minimize/close functionality +- [ ] Connect to notification manager +- [ ] Implement "Join Now" action +- [ ] Implement "Decline" action +- [ ] Add animations + +### Phase 4: Session History (12-16 hours) + +- [ ] Create `SessionHistoryManager.ts` service +- [ ] Implement local storage persistence +- [ ] Create `SessionHistory.tsx` panel +- [ ] Display recent sessions list +- [ ] Implement favorites toggle +- [ ] Create `SessionHistoryItem.tsx` component +- [ ] Implement rejoin from history +- [ ] Add "Clear History" option + +### Phase 5: Integration (10-12 hours) + +- [ ] Add session info API to signaling server +- [ ] Add "Copy Link" button to active sessions +- [ ] Add session history to Community panel +- [ ] Add favorites section +- [ ] Test cross-platform deep links +- [ ] Test with various link scenarios + +--- + +## Verification Steps + +- [ ] `opennoodl://` links open the app +- [ ] Session preview loads correctly +- [ ] Can join session from deep link +- [ ] Quick join widget appears for invites +- [ ] Can minimize/dismiss widget +- [ ] Session history saves correctly +- [ ] Can favorite sessions +- [ ] Can rejoin from history +- [ ] Copy link generates correct URL +- [ ] Works on Windows, Mac, Linux + +--- + +## Files to Create + +``` +packages/noodl-editor/src/editor/src/utils/ +├── DeepLinkHandler.ts # Protocol handler + +packages/noodl-editor/src/editor/src/services/ +├── SessionHistoryManager.ts # History persistence + +packages/noodl-editor/src/editor/src/views/ +├── SessionPreview/ +│ ├── SessionPreview.tsx # Preview dialog +│ └── SessionPreview.module.scss +├── QuickJoinWidget/ +│ ├── QuickJoinWidget.tsx # Floating invite widget +│ └── QuickJoinWidget.module.scss +├── SessionHistory/ +│ ├── SessionHistory.tsx # History panel +│ ├── SessionHistory.module.scss +│ └── SessionHistoryItem.tsx + +packages/noodl-editor/src/main/ +├── protocol-handler.js # Electron protocol registration +``` + +--- + +## Signaling Server API Extension + +Add endpoint for session info (without joining): + +``` +GET /session/{roomId}/info + +Response: +{ + "roomId": "abc123", + "title": "Building a User Dashboard", + "description": "Working on dashboard layout", + "host": { + "userId": "alice", + "name": "Alice Smith", + "avatar": "https://..." + }, + "participants": [ + { "userId": "bob", "name": "Bob", "avatar": "..." }, + ... + ], + "participantCount": 4, + "maxParticipants": 10, + "features": { + "audioEnabled": true, + "videoEnabled": false, + "screenShareEnabled": true + }, + "duration": 2700, // seconds + "createdAt": "2026-01-18T10:00:00Z" +} + +404 if session not found +``` + +--- + +## Platform-Specific Notes + +### macOS + +- Protocol registered via `app.setAsDefaultProtocolClient()` +- Already running app receives `open-url` event + +### Windows + +- Protocol registered in registry via `app.setAsDefaultProtocolClient()` +- Deep links passed via `process.argv` +- May require admin for first registration + +### Linux + +- Desktop file registration +- Varies by distribution +- Consider XDG protocol handler + +--- + +## Related Tasks + +- **GIT-007**: WebRTC Collaboration (provides joining mechanics) +- **GIT-008**: Notification System (delivers invites) +- **GIT-009**: Community UI (displays sessions) diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-011-integration-polish/README.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-011-integration-polish/README.md new file mode 100644 index 0000000..03079d9 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-011-integration-polish/README.md @@ -0,0 +1,344 @@ +# GIT-011: Integration & Polish + +## Overview + +**Priority:** High +**Estimated Hours:** 61-82 +**Dependencies:** All previous tasks (GIT-005 through GIT-010) +**Status:** 🔴 Not Started + +Final integration, testing, documentation, and marketing preparation for the live collaboration and multi-community system. + +--- + +## Strategic Context + +This is the "putting it all together" phase. Before launch: + +- **Everything must work end-to-end** - No broken flows +- **Performance must be acceptable** - <3s connection, smooth sync +- **Documentation must be complete** - Users need to know how to use it +- **Servers must be deployed** - Infrastructure ready for production +- **Marketing must be ready** - Demo videos, blog posts, press kit + +--- + +## Implementation Tasks + +### 1. End-to-End Testing (20-25 hours) + +#### Test Plan Creation + +- [ ] Document all user flows +- [ ] Create test scenarios for each flow +- [ ] Define success criteria +- [ ] Set up test environment + +#### Collaboration Testing + +- [ ] Test session creation (host) +- [ ] Test session joining (guest) +- [ ] Test WebRTC connection establishment +- [ ] Test WebSocket fallback scenarios +- [ ] Test with 2, 5, 10+ participants +- [ ] Test audio/video functionality +- [ ] Test screen share (if enabled) + +#### Sync Testing + +- [ ] Test cursor position sync +- [ ] Test selection sync +- [ ] Test viewport sync +- [ ] Test node creation sync +- [ ] Test node deletion sync +- [ ] Test connection creation sync +- [ ] Test property editing sync +- [ ] Test undo/redo in collaboration + +#### Edge Cases + +- [ ] Test disconnection and reconnection +- [ ] Test host leaving (does session persist?) +- [ ] Test network quality degradation +- [ ] Test firewall scenarios +- [ ] Test notification delivery +- [ ] Test deep link handling + +#### Community Testing + +- [ ] Test adding custom community +- [ ] Test community switching +- [ ] Test component installation +- [ ] Test GitHub Discussions integration + +### 2. Performance Optimization (15-20 hours) + +#### Profiling + +- [ ] Profile WebRTC data channel usage +- [ ] Profile Yjs document size +- [ ] Profile render performance during sync +- [ ] Profile memory usage over time + +#### Optimizations + +- [ ] Implement cursor position throttling (50ms) +- [ ] Add viewport culling for remote cursors +- [ ] Batch Yjs updates where possible +- [ ] Implement lazy loading for community content +- [ ] Add virtual scrolling for long lists +- [ ] Optimize GitHub API calls (caching) + +#### Benchmarks + +- [ ] Measure time to establish connection (target: <3s) +- [ ] Measure time for sync to stabilize (target: <5s) +- [ ] Measure cursor latency (target: <100ms) +- [ ] Measure CPU usage during collaboration + +### 3. Documentation (12-15 hours) + +#### User Documentation + +- [ ] Write "Getting Started with Collaboration" guide +- [ ] Document session creation and joining +- [ ] Document audio/video controls +- [ ] Write troubleshooting guide +- [ ] Create FAQ section + +#### Community Documentation + +- [ ] Write "Creating Your Own Community" guide +- [ ] Document community.json schema +- [ ] Document server deployment +- [ ] Create self-hosting guide + +#### Developer Documentation + +- [ ] Document CollaborationManager API +- [ ] Document NotificationManager API +- [ ] Document CommunityManager API +- [ ] Write architecture overview + +#### Video Tutorials + +- [ ] Record "Starting a Collaboration Session" (2-3 min) +- [ ] Record "Joining a Session" (1-2 min) +- [ ] Record "Setting Up Your Community" (5-7 min) + +### 4. Server Deployment (6-10 hours) + +#### Official Servers + +- [ ] Deploy signaling server to production +- [ ] Deploy sync server to production +- [ ] Deploy notification server to production +- [ ] Configure TURN server (Coturn or service) +- [ ] Configure SSL certificates +- [ ] Set up domain names + +#### Monitoring & Observability + +- [ ] Set up Prometheus metrics collection +- [ ] Configure Grafana dashboards +- [ ] Set up alerting (PagerDuty/Discord/email) +- [ ] Configure log aggregation +- [ ] Set up uptime monitoring + +#### Operations + +- [ ] Create deployment runbooks +- [ ] Document scaling procedures +- [ ] Set up backup systems (for notification server) +- [ ] Perform load testing +- [ ] Document incident response + +### 5. Marketing Preparation (8-12 hours) + +#### Demo Materials + +- [ ] Create 3-5 minute demo video +- [ ] Create GIF demonstrations +- [ ] Prepare live demo environment + +#### Content + +- [ ] Write announcement blog post +- [ ] Write technical deep-dive blog post +- [ ] Create social media graphics +- [ ] Write Twitter/X thread +- [ ] Prepare Product Hunt submission + +#### Press Kit + +- [ ] Create feature comparison matrix +- [ ] Compile screenshots +- [ ] Write press release +- [ ] Prepare case studies + +--- + +## Verification Steps + +### Functional Verification + +- [ ] All automated tests pass +- [ ] All manual test scenarios complete +- [ ] No critical bugs remaining +- [ ] Performance benchmarks met + +### Documentation Verification + +- [ ] Documentation is complete +- [ ] Documentation is accurate +- [ ] All links work +- [ ] Code examples run + +### Infrastructure Verification + +- [ ] Servers deployed and healthy +- [ ] Monitoring active +- [ ] Alerts configured +- [ ] Backup systems tested + +### Security Verification + +- [ ] Security audit passed +- [ ] Privacy compliance verified +- [ ] Rate limiting configured +- [ ] Authentication working + +--- + +## Launch Checklist + +### Pre-Launch (T-7 days) + +- [ ] Feature freeze +- [ ] Final testing round +- [ ] Documentation review +- [ ] Marketing materials approved + +### Launch Day (T-0) + +- [ ] Feature flag enabled (if using) +- [ ] Blog post published +- [ ] Social media posts scheduled +- [ ] Team available for support + +### Post-Launch (T+1 to T+7) + +- [ ] Monitor server health +- [ ] Track error rates +- [ ] Respond to user feedback +- [ ] Fix critical issues immediately +- [ ] Document lessons learned + +--- + +## Success Metrics + +### Adoption Metrics + +| Metric | Target | Measurement | +| ------------------------------ | ----------- | ----------- | +| Communities created | 10+ | First month | +| Collaboration sessions started | 100+ | First month | +| Average session duration | 15+ minutes | - | +| Components shared | 50+ | First month | + +### Technical Metrics + +| Metric | Target | +| ------------------------------ | ---------- | +| WebRTC connection success rate | >95% | +| Average connection time | <3 seconds | +| WebSocket fallback rate | <10% | +| Notification delivery rate | >99% | +| Server uptime | 99.9% | + +### User Satisfaction + +| Metric | Target | +| ------------------------------------- | ------- | +| Session completion rate | >80% | +| Feature usage (audio, cursor sharing) | Tracked | +| Net Promoter Score | >50 | + +--- + +## Risk Mitigation + +### Technical Risks + +| Risk | Mitigation | +| ---------------------------------- | ----------------------------------------- | +| WebRTC fails in corporate networks | Automatic WebSocket fallback, TURN server | +| Server overload at launch | Load testing, auto-scaling, rate limiting | +| Data loss during sync | Yjs CRDT guarantees, periodic snapshots | +| Security vulnerabilities | Security audit, penetration testing | + +### Business Risks + +| Risk | Mitigation | +| ----------------------- | ------------------------------------------- | +| Low adoption | Marketing push, tutorial content | +| Community fragmentation | Make official community compelling | +| Server costs | P2P-first architecture, self-hosting option | +| Support overwhelm | Good documentation, FAQ, community forums | + +--- + +## Files to Create/Update + +``` +Documentation: +docs/ +├── collaboration/ +│ ├── getting-started.md +│ ├── sessions.md +│ ├── troubleshooting.md +│ └── faq.md +├── community/ +│ ├── creating-community.md +│ ├── community-json-schema.md +│ └── self-hosting.md +└── api/ + ├── collaboration-manager.md + ├── notification-manager.md + └── community-manager.md + +Marketing: +marketing/ +├── demo-video-script.md +├── blog-post-announcement.md +├── blog-post-technical.md +├── social-media-kit/ +│ ├── twitter-thread.md +│ └── graphics/ +└── press-kit/ + ├── press-release.md + ├── screenshots/ + └── feature-comparison.md +``` + +--- + +## Definition of Done + +This task (and the entire GIT-5 through GIT-11 series) is complete when: + +1. ✅ All features implemented and tested +2. ✅ Performance benchmarks met +3. ✅ Documentation complete +4. ✅ Servers deployed and monitored +5. ✅ Marketing materials ready +6. ✅ Launch checklist complete +7. ✅ No critical bugs outstanding + +--- + +## Related Tasks + +- **GIT-005 through GIT-010**: All prior tasks in this series +- **Future**: Advanced features (screen sharing, session recording, AI integration) diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Live-Collaboration-Community-System.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Live-Collaboration-Community-System.md new file mode 100644 index 0000000..ab3c6a8 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Live-Collaboration-Community-System.md @@ -0,0 +1,2894 @@ +# Live Collaboration & Multi-Community System +## Task Documentation: GIT-5 through GIT-11 + +**Series**: GitHub Integration (Advanced Phase) +**Total Estimated Hours**: 431-572 hours +**Dependencies**: GitHub OAuth (GIT-1 through GIT-4) + +--- + +## Overview + +This series transforms Nodegx into a collaborative platform with multi-community support, live real-time collaboration (Google Docs for visual programming), and persistent notification system. Users can participate in the official Nodegx community or fork and operate their own private communities with self-hosted infrastructure. + +### Strategic Goals + +1. **Community Ownership**: BYOB philosophy applied to communities - users can fork, self-host, and control their own spaces +2. **Live Collaboration**: Real-time multi-user editing with audio/video via WebRTC (industry first for desktop visual dev tools) +3. **Persistent Notifications**: Cross-session notification system for community events and collaboration invites +4. **Network Effects**: Component sharing, tutorial discovery, and collaboration sessions drive user engagement +5. **Enterprise Ready**: Private communities for companies using Nodegx as internal tooling + +### Key Features + +- Multi-community management (join multiple communities simultaneously) +- Live collaboration sessions with audio/video chat +- Public and private collaboration sessions +- Direct user invitations with persistent notifications +- Community template repository (forkable) +- Self-hosted infrastructure (signaling, sync, relay servers) +- Real-time presence (see who's online, what they're working on) +- Component library discovery across communities +- Tutorial and showcase feeds +- Job board integration + +--- + +## GIT-5: Community Infrastructure & Template Repository + +**Priority**: High +**Estimated Hours**: 60-80 +**Dependencies**: GIT-1 (GitHub OAuth) + +### Purpose + +Create the foundational community infrastructure that enables users to fork and operate their own Nodegx communities. This includes the community template repository structure, server deployment templates, and community management system. + +### Technical Requirements + +#### 1. Community Template Repository + +Create a new repository: `nodegx-community` that serves as the template for all communities. + +**Repository Structure:** + +``` +nodegx-community/ +├── .github/ +│ ├── workflows/ +│ │ ├── validate-community.yml # Validates community.json schema +│ │ ├── sync-discussions.yml # Syncs discussion metadata +│ │ ├── update-feeds.yml # Updates tutorial/showcase feeds +│ │ └── publish-sessions.yml # Updates public session list +│ └── ISSUE_TEMPLATE/ +│ ├── component-submission.yml +│ ├── tutorial-submission.yml +│ └── session-request.yml +│ +├── community.json # Community metadata (CRITICAL) +├── README.md # Community home page +├── CODE_OF_CONDUCT.md # Community guidelines +├── CONTRIBUTING.md # How to contribute +│ +├── components/ +│ ├── README.md # Component library guide +│ ├── featured.json # Curated components +│ └── registry.json # All registered components +│ +├── tutorials/ +│ ├── README.md # Learning resources hub +│ ├── beginner/ +│ │ └── index.json # Beginner tutorials metadata +│ ├── intermediate/ +│ │ └── index.json +│ └── advanced/ +│ └── index.json +│ +├── showcase/ +│ ├── README.md # Featured projects +│ ├── projects.json # Project submissions +│ └── templates/ +│ └── project-template.json +│ +├── jobs/ +│ ├── README.md # Job board guide +│ └── listings.json # Current listings (PRs to add) +│ +├── collaboration/ +│ ├── README.md # Collaboration guide +│ ├── public-sessions.json # Active public sessions +│ └── session-template.json # Template for session metadata +│ +└── config/ + ├── servers.json # Server endpoints + ├── features.json # Feature flags + └── notifications.json # Notification settings +``` + +#### 2. Community Metadata Schema + +**File: `community.json`** + +This file is the single source of truth for community configuration. + +```json +{ + "$schema": "https://nodegx.org/schemas/community.v1.json", + "version": "1.0.0", + "community": { + "id": "nodegx-official", + "name": "Nodegx Official Community", + "description": "The official Nodegx visual programming community", + "type": "public", + "owner": { + "github": "nodegx", + "name": "Visual Hive", + "website": "https://visualhive.com", + "contact": "community@visualhive.com" + }, + "repository": "https://github.com/nodegx/nodegx-community", + "createdAt": "2025-01-18T00:00:00Z", + "updatedAt": "2025-01-18T00:00:00Z" + }, + "servers": { + "signaling": { + "url": "wss://signal.nodegx.com", + "healthCheck": "https://signal.nodegx.com/health" + }, + "sync": { + "url": "wss://sync.nodegx.com", + "healthCheck": "https://sync.nodegx.com/health" + }, + "turn": { + "urls": ["turn:relay.nodegx.com:3478"], + "username": "nodegx", + "credentialType": "password" + }, + "notifications": { + "url": "wss://notify.nodegx.com", + "healthCheck": "https://notify.nodegx.com/health" + } + }, + "features": { + "discussions": true, + "components": true, + "tutorials": true, + "showcase": true, + "jobs": true, + "collaboration": { + "enabled": true, + "publicSessions": true, + "privateSessions": true, + "maxSessionSize": 10, + "audioEnabled": true, + "videoEnabled": false, + "screenShareEnabled": true, + "requireAuth": true + }, + "notifications": { + "enabled": true, + "channels": ["discussions", "sessions", "components", "invites"], + "persistDays": 30 + } + }, + "branding": { + "primaryColor": "#FF6B6B", + "secondaryColor": "#4ECDC4", + "logo": "https://nodegx.com/community-logo.png", + "favicon": "https://nodegx.com/favicon.ico", + "customCSS": null + }, + "moderation": { + "moderators": ["richardthompson", "visualhive"], + "requireApproval": { + "components": false, + "tutorials": true, + "showcaseProjects": true, + "jobs": false + }, + "autoModerationEnabled": true, + "bannedUsers": [] + }, + "integrations": { + "github": { + "org": "nodegx", + "discussionsRepo": "nodegx/nodegx-community", + "issuesRepo": "nodegx/nodegx" + }, + "discord": { + "enabled": false, + "webhookUrl": null, + "serverId": null + }, + "slack": { + "enabled": false, + "webhookUrl": null + } + }, + "limits": { + "maxComponentsPerUser": 50, + "maxSessionsPerUser": 5, + "maxInvitesPerSession": 20, + "rateLimit": { + "sessionsPerHour": 10, + "invitesPerHour": 50 + } + } +} +``` + +#### 3. GitHub Actions Workflows + +**File: `.github/workflows/validate-community.yml`** + +```yaml +name: Validate Community Configuration + +on: + push: + paths: + - 'community.json' + - 'components/**' + - 'collaboration/**' + pull_request: + paths: + - 'community.json' + - 'components/**' + - 'collaboration/**' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Validate community.json schema + run: | + npx ajv-cli validate \ + -s https://nodegx.org/schemas/community.v1.json \ + -d community.json + + - name: Validate component registry + run: | + node scripts/validate-components.js + + - name: Validate collaboration sessions + run: | + node scripts/validate-sessions.js + + - name: Check server health + run: | + node scripts/check-server-health.js +``` + +**File: `.github/workflows/sync-discussions.yml`** + +```yaml +name: Sync GitHub Discussions + +on: + schedule: + - cron: '*/15 * * * *' # Every 15 minutes + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Fetch latest discussions + run: | + node scripts/sync-discussions.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update discussions metadata + run: | + git config user.name "Nodegx Bot" + git config user.email "bot@nodegx.com" + git add discussions/ + git diff --quiet && git diff --staged --quiet || \ + (git commit -m "Update discussions metadata" && git push) +``` + +#### 4. Validation Scripts + +**File: `scripts/validate-community.js`** + +```javascript +const fs = require('fs'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); + +async function validateCommunity() { + const ajv = new Ajv({ allErrors: true }); + addFormats(ajv); + + // Load schema + const schema = JSON.parse( + fs.readFileSync('./schemas/community.v1.json', 'utf8') + ); + + // Load community config + const config = JSON.parse( + fs.readFileSync('./community.json', 'utf8') + ); + + const validate = ajv.compile(schema); + const valid = validate(config); + + if (!valid) { + console.error('Community configuration is invalid:'); + console.error(validate.errors); + process.exit(1); + } + + console.log('✓ Community configuration is valid'); + + // Additional validation + await validateServers(config.servers); + await validateModerators(config.moderation.moderators); + + console.log('✓ All validations passed'); +} + +async function validateServers(servers) { + // Check if servers are reachable + const checks = [ + fetch(servers.signaling.healthCheck), + fetch(servers.sync.healthCheck), + fetch(servers.notifications.healthCheck) + ]; + + const results = await Promise.allSettled(checks); + + results.forEach((result, index) => { + const serverNames = ['signaling', 'sync', 'notifications']; + if (result.status === 'rejected') { + console.warn(`⚠ ${serverNames[index]} server health check failed`); + } + }); +} + +async function validateModerators(moderators) { + // Verify moderators are valid GitHub users + for (const username of moderators) { + try { + const response = await fetch(`https://api.github.com/users/${username}`); + if (!response.ok) { + throw new Error(`User ${username} not found`); + } + } catch (err) { + console.error(`Invalid moderator: ${username}`); + process.exit(1); + } + } +} + +validateCommunity(); +``` + +#### 5. Editor Integration - Community Manager + +**File: `packages/noodl-editor/src/editor/src/services/CommunityManager.ts`** + +```typescript +import { Octokit } from '@octokit/rest'; +import EventEmitter from 'events'; + +interface Community { + id: string; + name: string; + description: string; + type: 'public' | 'private'; + repository: string; + config: CommunityConfig; + isActive: boolean; + lastSync: Date; +} + +interface CommunityConfig { + servers: { + signaling: { url: string; healthCheck: string }; + sync: { url: string; healthCheck: string }; + turn: { urls: string[]; username: string }; + notifications: { url: string; healthCheck: string }; + }; + features: { + collaboration: { + enabled: boolean; + publicSessions: boolean; + privateSessions: boolean; + }; + notifications: { + enabled: boolean; + channels: string[]; + }; + }; +} + +export class CommunityManager extends EventEmitter { + private communities: Map = new Map(); + private activeCommunity: string | null = null; + private octokit: Octokit; + private syncInterval: NodeJS.Timeout | null = null; + + constructor(octokitInstance: Octokit) { + super(); + this.octokit = octokitInstance; + } + + async initialize() { + // Load communities from user preferences + const saved = this.loadSavedCommunities(); + + // Add official Nodegx community by default + await this.addCommunity('https://github.com/nodegx/nodegx-community'); + + // Add user's saved communities + for (const repoUrl of saved) { + await this.addCommunity(repoUrl); + } + + // Start background sync + this.startBackgroundSync(); + + this.emit('initialized'); + } + + async addCommunity(repoUrl: string): Promise { + // Parse GitHub repo URL + const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/); + if (!match) { + throw new Error('Invalid GitHub repository URL'); + } + + const [, owner, repo] = match; + + try { + // Fetch community.json from repo + const { data } = await this.octokit.repos.getContent({ + owner, + repo, + path: 'community.json' + }); + + if (!('content' in data)) { + throw new Error('community.json not found'); + } + + const content = Buffer.from(data.content, 'base64').toString(); + const config = JSON.parse(content); + + // Validate schema + await this.validateCommunityConfig(config); + + // Check server health + const serversHealthy = await this.checkServerHealth(config.servers); + + const community: Community = { + id: config.community.id, + name: config.community.name, + description: config.community.description, + type: config.community.type, + repository: repoUrl, + config, + isActive: serversHealthy, + lastSync: new Date() + }; + + this.communities.set(community.id, community); + this.saveCommunities(); + + this.emit('community-added', community); + + return community; + + } catch (err) { + throw new Error(`Failed to add community: ${err.message}`); + } + } + + async removeCommunity(communityId: string) { + const community = this.communities.get(communityId); + if (!community) { + throw new Error('Community not found'); + } + + // Can't remove official community + if (communityId === 'nodegx-official') { + throw new Error('Cannot remove official Nodegx community'); + } + + this.communities.delete(communityId); + this.saveCommunities(); + + this.emit('community-removed', communityId); + } + + getCommunities(): Community[] { + return Array.from(this.communities.values()); + } + + getActiveCommunity(): Community | null { + if (!this.activeCommunity) return null; + return this.communities.get(this.activeCommunity) || null; + } + + setActiveCommunity(communityId: string) { + const community = this.communities.get(communityId); + if (!community) { + throw new Error('Community not found'); + } + + this.activeCommunity = communityId; + this.emit('active-community-changed', community); + } + + async syncCommunity(communityId: string) { + const community = this.communities.get(communityId); + if (!community) return; + + const [, owner, repo] = community.repository.match( + /github\.com\/([^\/]+)\/([^\/]+)/ + )!; + + try { + // Re-fetch community.json + const { data } = await this.octokit.repos.getContent({ + owner, + repo, + path: 'community.json' + }); + + if (!('content' in data)) return; + + const content = Buffer.from(data.content, 'base64').toString(); + const config = JSON.parse(content); + + // Update community config + community.config = config; + community.lastSync = new Date(); + + // Re-check server health + community.isActive = await this.checkServerHealth(config.servers); + + this.emit('community-synced', community); + + } catch (err) { + console.error(`Failed to sync community ${communityId}:`, err); + } + } + + private async checkServerHealth(servers: CommunityConfig['servers']): Promise { + try { + const checks = await Promise.all([ + fetch(servers.signaling.healthCheck), + fetch(servers.sync.healthCheck), + fetch(servers.notifications.healthCheck) + ]); + + return checks.every(r => r.ok); + } catch { + return false; + } + } + + private async validateCommunityConfig(config: any) { + // TODO: Use AJV to validate against schema + if (!config.community || !config.servers) { + throw new Error('Invalid community configuration'); + } + } + + private startBackgroundSync() { + // Sync all communities every 15 minutes + this.syncInterval = setInterval(() => { + this.communities.forEach((_, id) => { + this.syncCommunity(id); + }); + }, 15 * 60 * 1000); + } + + private loadSavedCommunities(): string[] { + // Load from user preferences + const prefs = window.ProjectModel?.prefs; + return prefs?.get('communities') || []; + } + + private saveCommunities() { + const repos = Array.from(this.communities.values()) + .filter(c => c.id !== 'nodegx-official') + .map(c => c.repository); + + window.ProjectModel?.prefs.set('communities', repos); + } + + destroy() { + if (this.syncInterval) { + clearInterval(this.syncInterval); + } + } +} +``` + +### Implementation Tasks + +- [ ] Create `nodegx-community` template repository with full structure +- [ ] Implement `community.json` schema and validation +- [ ] Create GitHub Actions workflows for validation and sync +- [ ] Implement `CommunityManager` service in editor +- [ ] Create community settings UI panel +- [ ] Add "Add Community" flow (paste GitHub URL) +- [ ] Add "Create Community" flow (fork template) +- [ ] Implement background sync for community configs +- [ ] Add server health monitoring +- [ ] Create community switcher UI component + +### Verification Steps + +- [ ] Can fork `nodegx-community` template and customize `community.json` +- [ ] GitHub Actions validate configuration on push +- [ ] Editor can add community by pasting GitHub URL +- [ ] Editor validates community config and checks server health +- [ ] Can switch between multiple communities +- [ ] Background sync updates community configs +- [ ] Invalid communities show error states +- [ ] Can remove communities (except official) + +### Notes + +- Official Nodegx community cannot be removed (hardcoded) +- Community configs are cached locally for offline access +- Server health checks run every sync cycle +- Failed health checks show warning but don't remove community + +--- + +## GIT-6: Server Infrastructure (Signaling, Sync, Notifications) + +**Priority**: Critical +**Estimated Hours**: 80-100 +**Dependencies**: GIT-5 + +### Purpose + +Build the three core server components required for live collaboration and notifications: Signaling Server (WebRTC peer discovery), Sync Server (WebSocket fallback), and Notification Server (persistent cross-session notifications). + +### Technical Requirements + +#### 1. Signaling Server (WebRTC Peer Discovery) + +Minimal WebSocket server that helps peers find each other for WebRTC connections. + +**Repository: `nodegx-signaling-server`** + +**File: `index.js`** + +```javascript +/** + * Nodegx Signaling Server + * Helps WebRTC peers discover each other + * NO project data passes through this server + */ + +const WebSocket = require('ws'); +const http = require('http'); +const url = require('url'); +const crypto = require('crypto'); + +const PORT = process.env.PORT || 4444; +const HEARTBEAT_INTERVAL = 30000; // 30 seconds +const MAX_ROOM_SIZE = parseInt(process.env.MAX_ROOM_SIZE) || 20; + +// Data structures +const rooms = new Map(); // roomId -> Set +const userToWs = new Map(); // userId -> WebSocket +const wsToUser = new Map(); // WebSocket -> userId + +// Metrics +const metrics = { + totalConnections: 0, + activeConnections: 0, + activeRooms: 0, + messagesRelayed: 0, + startTime: Date.now() +}; + +// HTTP server for health checks +const server = http.createServer((req, res) => { + const pathname = url.parse(req.url).pathname; + + if (pathname === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + status: 'healthy', + uptime: Math.floor((Date.now() - metrics.startTime) / 1000), + metrics: { + ...metrics, + activeRooms: rooms.size, + roomDetails: Array.from(rooms.entries()).map(([id, peers]) => ({ + roomId: id, + peerCount: peers.size + })) + } + })); + } else if (pathname === '/metrics') { + // Prometheus-compatible metrics + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(` +# HELP nodegx_signaling_connections Total connections +# TYPE nodegx_signaling_connections counter +nodegx_signaling_connections_total ${metrics.totalConnections} +nodegx_signaling_connections_active ${metrics.activeConnections} + +# HELP nodegx_signaling_rooms Active rooms +# TYPE nodegx_signaling_rooms gauge +nodegx_signaling_rooms_active ${rooms.size} + +# HELP nodegx_signaling_messages Messages relayed +# TYPE nodegx_signaling_messages counter +nodegx_signaling_messages_total ${metrics.messagesRelayed} + `.trim()); + } else { + res.writeHead(404); + res.end('Not Found'); + } +}); + +// WebSocket server +const wss = new WebSocket.Server({ server }); + +wss.on('connection', (ws, req) => { + metrics.totalConnections++; + metrics.activeConnections++; + + let currentRoom = null; + let peerInfo = null; + + // Heartbeat to detect dead connections + ws.isAlive = true; + ws.on('pong', () => { ws.isAlive = true; }); + + ws.on('message', (data) => { + try { + const msg = JSON.parse(data); + handleMessage(ws, msg); + } catch (err) { + sendError(ws, 'Invalid JSON'); + } + }); + + ws.on('close', () => { + metrics.activeConnections--; + + if (currentRoom && peerInfo) { + leaveRoom(currentRoom, peerInfo.peerId); + } + + if (peerInfo) { + userToWs.delete(peerInfo.userId); + wsToUser.delete(ws); + } + }); + + function handleMessage(ws, msg) { + metrics.messagesRelayed++; + + switch (msg.type) { + case 'join': + handleJoin(ws, msg); + break; + case 'leave': + handleLeave(ws, msg); + break; + case 'signal': + handleSignal(ws, msg); + break; + case 'invite': + handleInvite(ws, msg); + break; + default: + sendError(ws, 'Unknown message type'); + } + } + + function handleJoin(ws, msg) { + const { room, peerId, userId, metadata } = msg; + + if (!room || !peerId || !userId) { + return sendError(ws, 'Missing required fields'); + } + + // Create room if doesn't exist + if (!rooms.has(room)) { + rooms.set(room, new Set()); + metrics.activeRooms++; + } + + const roomPeers = rooms.get(room); + + // Check room size limit + if (roomPeers.size >= MAX_ROOM_SIZE) { + return sendError(ws, 'Room is full'); + } + + // Store peer info + peerInfo = { peerId, userId, metadata, ws }; + currentRoom = room; + roomPeers.add(peerInfo); + + userToWs.set(userId, ws); + wsToUser.set(ws, userId); + + // Notify peer of successful join + send(ws, { + type: 'joined', + room, + peerId, + peers: Array.from(roomPeers) + .filter(p => p.peerId !== peerId) + .map(p => ({ + peerId: p.peerId, + userId: p.userId, + metadata: p.metadata + })) + }); + + // Notify other peers + broadcast(room, { + type: 'peer-joined', + peerId, + userId, + metadata + }, peerId); + } + + function handleLeave(ws, msg) { + if (currentRoom && peerInfo) { + leaveRoom(currentRoom, peerInfo.peerId); + currentRoom = null; + peerInfo = null; + } + } + + function handleSignal(ws, msg) { + const { to, signal } = msg; + + if (!to || !signal) { + return sendError(ws, 'Missing required fields'); + } + + if (!currentRoom || !peerInfo) { + return sendError(ws, 'Not in a room'); + } + + // Find target peer in same room + const roomPeers = rooms.get(currentRoom); + const targetPeer = Array.from(roomPeers).find(p => p.peerId === to); + + if (!targetPeer) { + return sendError(ws, 'Target peer not found'); + } + + // Relay signal to target + send(targetPeer.ws, { + type: 'signal', + from: peerInfo.peerId, + signal + }); + } + + function handleInvite(ws, msg) { + const { targetUserId, roomId, sessionInfo } = msg; + + if (!targetUserId || !roomId) { + return sendError(ws, 'Missing required fields'); + } + + // Find target user's WebSocket + const targetWs = userToWs.get(targetUserId); + + if (targetWs) { + // User is online, send direct invite + send(targetWs, { + type: 'invite', + from: peerInfo.userId, + roomId, + sessionInfo + }); + + send(ws, { + type: 'invite-sent', + to: targetUserId, + delivered: true + }); + } else { + // User is offline, notify sender + send(ws, { + type: 'invite-sent', + to: targetUserId, + delivered: false, + reason: 'User offline' + }); + } + } + + function leaveRoom(room, peerId) { + const roomPeers = rooms.get(room); + if (!roomPeers) return; + + // Remove peer + const peer = Array.from(roomPeers).find(p => p.peerId === peerId); + if (peer) { + roomPeers.delete(peer); + + // Notify others + broadcast(room, { + type: 'peer-left', + peerId + }, peerId); + + // Clean up empty room + if (roomPeers.size === 0) { + rooms.delete(room); + metrics.activeRooms--; + } + } + } + + function broadcast(room, message, excludePeerId = null) { + const roomPeers = rooms.get(room); + if (!roomPeers) return; + + roomPeers.forEach(peer => { + if (peer.peerId !== excludePeerId) { + send(peer.ws, message); + } + }); + } + + function send(ws, message) { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(message)); + } + } + + function sendError(ws, error) { + send(ws, { type: 'error', error }); + } +}); + +// Heartbeat to detect dead connections +const heartbeat = setInterval(() => { + wss.clients.forEach(ws => { + if (!ws.isAlive) { + const userId = wsToUser.get(ws); + if (userId) { + userToWs.delete(userId); + wsToUser.delete(ws); + } + return ws.terminate(); + } + + ws.isAlive = false; + ws.ping(); + }); +}, HEARTBEAT_INTERVAL); + +wss.on('close', () => { + clearInterval(heartbeat); +}); + +server.listen(PORT, () => { + console.log(`Signaling server running on port ${PORT}`); +}); +``` + +**File: `package.json`** + +```json +{ + "name": "nodegx-signaling-server", + "version": "1.0.0", + "description": "WebRTC signaling server for Nodegx collaboration", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js" + }, + "dependencies": { + "ws": "^8.14.0" + }, + "devDependencies": { + "nodemon": "^3.0.0" + } +} +``` + +**File: `Dockerfile`** + +```dockerfile +FROM node:18-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --only=production + +COPY . . + +EXPOSE 4444 + +CMD ["node", "index.js"] +``` + +**File: `railway.json`** (One-click deploy) + +```json +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "DOCKERFILE" + }, + "deploy": { + "startCommand": "node index.js", + "healthcheckPath": "/health", + "restartPolicyType": "ON_FAILURE" + } +} +``` + +#### 2. Sync Server (WebSocket Fallback) + +Traditional WebSocket server using Yjs for CRDT synchronization. + +**Repository: `nodegx-sync-server`** + +**File: `index.js`** + +```javascript +/** + * Nodegx Sync Server + * WebSocket-based CRDT sync using Yjs + * Fallback when WebRTC P2P fails + */ + +const WebSocket = require('ws'); +const http = require('http'); +const Y = require('yjs'); +const { setupWSConnection } = require('y-websocket/bin/utils'); + +const PORT = process.env.PORT || 4445; +const PERSIST_DIR = process.env.PERSIST_DIR || './data'; +const ENABLE_PERSISTENCE = process.env.ENABLE_PERSISTENCE !== 'false'; + +// Persistence (optional) +const persistence = ENABLE_PERSISTENCE + ? require('y-leveldb').LeveldbPersistence(PERSIST_DIR) + : null; + +// Metrics +const metrics = { + activeConnections: 0, + activeDocuments: 0, + totalUpdates: 0 +}; + +// HTTP server for health checks +const server = http.createServer((req, res) => { + if (req.url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + status: 'healthy', + metrics + })); + } else { + res.writeHead(404); + res.end('Not Found'); + } +}); + +// WebSocket server +const wss = new WebSocket.Server({ server }); + +wss.on('connection', (ws, req) => { + metrics.activeConnections++; + + // Yjs handles all the CRDT magic + setupWSConnection(ws, req, { + persistence, + gc: true // Enable garbage collection + }); + + ws.on('close', () => { + metrics.activeConnections--; + }); +}); + +server.listen(PORT, () => { + console.log(`Sync server running on port ${PORT}`); + console.log(`Persistence: ${ENABLE_PERSISTENCE ? 'enabled' : 'disabled'}`); +}); +``` + +**File: `package.json`** + +```json +{ + "name": "nodegx-sync-server", + "version": "1.0.0", + "description": "Yjs WebSocket sync server for Nodegx", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "ws": "^8.14.0", + "yjs": "^13.6.0", + "y-websocket": "^1.5.0", + "y-leveldb": "^0.1.2" + } +} +``` + +#### 3. Notification Server (Persistent Notifications) + +Server for managing cross-session notifications (invites, community events, etc.). + +**Repository: `nodegx-notification-server`** + +**File: `index.js`** + +```javascript +/** + * Nodegx Notification Server + * Persistent notifications across sessions + * Stores invites, mentions, community events + */ + +const WebSocket = require('ws'); +const http = require('http'); +const { Low } = require('lowdb'); +const { JSONFile } = require('lowdb/node'); +const crypto = require('crypto'); + +const PORT = process.env.PORT || 4446; +const DB_FILE = process.env.DB_FILE || './notifications.json'; +const NOTIFICATION_TTL_DAYS = parseInt(process.env.TTL_DAYS) || 30; + +// Database setup +const adapter = new JSONFile(DB_FILE); +const db = new Low(adapter); + +async function initDB() { + await db.read(); + db.data ||= { notifications: [], users: {} }; + await db.write(); +} + +// Data structures +const userConnections = new Map(); // userId -> Set + +// HTTP server +const server = http.createServer((req, res) => { + if (req.url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + status: 'healthy', + notificationCount: db.data.notifications.length, + connectedUsers: userConnections.size + })); + } else { + res.writeHead(404); + res.end('Not Found'); + } +}); + +// WebSocket server +const wss = new WebSocket.Server({ server }); + +wss.on('connection', (ws, req) => { + let userId = null; + + ws.on('message', async (data) => { + try { + const msg = JSON.parse(data); + await handleMessage(ws, msg); + } catch (err) { + sendError(ws, 'Invalid message'); + } + }); + + ws.on('close', () => { + if (userId) { + const connections = userConnections.get(userId); + if (connections) { + connections.delete(ws); + if (connections.size === 0) { + userConnections.delete(userId); + } + } + } + }); + + async function handleMessage(ws, msg) { + switch (msg.type) { + case 'authenticate': + await handleAuth(ws, msg); + break; + case 'get-notifications': + await handleGetNotifications(ws, msg); + break; + case 'mark-read': + await handleMarkRead(ws, msg); + break; + case 'send-notification': + await handleSendNotification(ws, msg); + break; + case 'delete-notification': + await handleDeleteNotification(ws, msg); + break; + } + } + + async function handleAuth(ws, msg) { + userId = msg.userId; + + if (!userId) { + return sendError(ws, 'Invalid userId'); + } + + // Add connection to user's set + if (!userConnections.has(userId)) { + userConnections.set(userId, new Set()); + } + userConnections.get(userId).add(ws); + + send(ws, { type: 'authenticated', userId }); + + // Send pending notifications + const notifications = await getNotificationsForUser(userId); + send(ws, { + type: 'notifications', + notifications + }); + } + + async function handleGetNotifications(ws, msg) { + if (!userId) return sendError(ws, 'Not authenticated'); + + const notifications = await getNotificationsForUser(userId); + send(ws, { + type: 'notifications', + notifications + }); + } + + async function handleMarkRead(ws, msg) { + if (!userId) return sendError(ws, 'Not authenticated'); + + const { notificationId } = msg; + + await db.read(); + const notification = db.data.notifications.find(n => n.id === notificationId); + + if (notification && notification.userId === userId) { + notification.read = true; + notification.readAt = new Date().toISOString(); + await db.write(); + + send(ws, { + type: 'notification-updated', + notification + }); + } + } + + async function handleSendNotification(ws, msg) { + if (!userId) return sendError(ws, 'Not authenticated'); + + const { targetUserId, type, data } = msg; + + const notification = { + id: crypto.randomUUID(), + userId: targetUserId, + fromUserId: userId, + type, + data, + read: false, + createdAt: new Date().toISOString(), + expiresAt: new Date(Date.now() + NOTIFICATION_TTL_DAYS * 24 * 60 * 60 * 1000).toISOString() + }; + + await db.read(); + db.data.notifications.push(notification); + await db.write(); + + // Send to target user if online + const targetConnections = userConnections.get(targetUserId); + if (targetConnections) { + targetConnections.forEach(targetWs => { + send(targetWs, { + type: 'notification', + notification + }); + }); + } + + send(ws, { + type: 'notification-sent', + notificationId: notification.id + }); + } + + async function handleDeleteNotification(ws, msg) { + if (!userId) return sendError(ws, 'Not authenticated'); + + const { notificationId } = msg; + + await db.read(); + const index = db.data.notifications.findIndex( + n => n.id === notificationId && n.userId === userId + ); + + if (index !== -1) { + db.data.notifications.splice(index, 1); + await db.write(); + + send(ws, { + type: 'notification-deleted', + notificationId + }); + } + } + + async function getNotificationsForUser(userId) { + await db.read(); + + const now = new Date(); + + // Clean up expired notifications + db.data.notifications = db.data.notifications.filter(n => { + return new Date(n.expiresAt) > now; + }); + await db.write(); + + // Return user's notifications + return db.data.notifications + .filter(n => n.userId === userId) + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + } + + function send(ws, message) { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(message)); + } + } + + function sendError(ws, error) { + send(ws, { type: 'error', error }); + } +}); + +// Cleanup expired notifications every hour +setInterval(async () => { + await db.read(); + const before = db.data.notifications.length; + const now = new Date(); + + db.data.notifications = db.data.notifications.filter(n => { + return new Date(n.expiresAt) > now; + }); + + if (db.data.notifications.length !== before) { + await db.write(); + console.log(`Cleaned up ${before - db.data.notifications.length} expired notifications`); + } +}, 60 * 60 * 1000); + +initDB().then(() => { + server.listen(PORT, () => { + console.log(`Notification server running on port ${PORT}`); + }); +}); +``` + +**File: `package.json`** + +```json +{ + "name": "nodegx-notification-server", + "version": "1.0.0", + "description": "Persistent notification server for Nodegx", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "ws": "^8.14.0", + "lowdb": "^6.1.0" + } +} +``` + +### Deployment Configuration + +**File: `docker-compose.yml`** (All three servers) + +```yaml +version: '3.8' + +services: + signaling: + build: ./nodegx-signaling-server + ports: + - "4444:4444" + environment: + - PORT=4444 + - MAX_ROOM_SIZE=20 + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:4444/health"] + interval: 30s + timeout: 10s + retries: 3 + + sync: + build: ./nodegx-sync-server + ports: + - "4445:4445" + environment: + - PORT=4445 + - ENABLE_PERSISTENCE=true + - PERSIST_DIR=/data + volumes: + - sync-data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:4445/health"] + interval: 30s + timeout: 10s + retries: 3 + + notifications: + build: ./nodegx-notification-server + ports: + - "4446:4446" + environment: + - PORT=4446 + - DB_FILE=/data/notifications.json + - TTL_DAYS=30 + volumes: + - notification-data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:4446/health"] + interval: 30s + timeout: 10s + retries: 3 + +volumes: + sync-data: + notification-data: +``` + +### Implementation Tasks + +- [ ] Create `nodegx-signaling-server` repository +- [ ] Create `nodegx-sync-server` repository +- [ ] Create `nodegx-notification-server` repository +- [ ] Implement signaling server with room management +- [ ] Implement sync server with Yjs integration +- [ ] Implement notification server with persistence +- [ ] Add health check endpoints to all servers +- [ ] Add Prometheus metrics endpoints +- [ ] Create Docker images for all servers +- [ ] Create one-click deploy configs (Railway, Render, Fly.io) +- [ ] Add deployment documentation +- [ ] Deploy official servers for Visual Hive +- [ ] Test server failover and recovery + +### Verification Steps + +- [ ] Signaling server helps peers find each other +- [ ] Sync server synchronizes Yjs documents +- [ ] Notification server stores and delivers notifications +- [ ] Health endpoints return 200 OK +- [ ] Metrics endpoints expose data +- [ ] Docker Compose brings up all services +- [ ] One-click deploy works on Railway/Render +- [ ] Servers handle connection failures gracefully +- [ ] Old notifications are cleaned up automatically + +### Notes + +- All servers designed to be stateless (horizontally scalable) +- Sync server can optionally persist data with LevelDB +- Notification server uses LowDB (can be replaced with Redis/PostgreSQL for scale) +- All servers include CORS headers for cross-origin requests +- WebSocket connections include heartbeat/ping-pong for dead connection detection + +--- + +## GIT-7: WebRTC Collaboration Client + +**Priority**: Critical +**Estimated Hours**: 100-130 +**Dependencies**: GIT-5, GIT-6 + +### Purpose + +Implement the client-side WebRTC collaboration system that enables real-time multi-user editing, cursor sharing, audio/video chat, and seamless fallback to WebSocket sync. + +### Technical Requirements + +#### 1. Collaboration Manager Service + +**File: `packages/noodl-editor/src/editor/src/services/CollaborationManager.ts`** + +```typescript +import * as Y from 'yjs'; +import { WebrtcProvider } from 'y-webrtc'; +import { WebsocketProvider } from 'y-websocket'; +import SimplePeer from 'simple-peer'; +import EventEmitter from 'events'; + +interface CollaborationSession { + id: string; + roomId: string; + projectId: string; + isHost: boolean; + isPublic: boolean; + title: string; + description?: string; + maxParticipants: number; + participants: Map; + createdAt: Date; +} + +interface Participant { + peerId: string; + userId: string; + name: string; + avatar?: string; + color: string; + cursor?: { x: number; y: number }; + selection?: { nodeId: string }; + viewport?: { x: number; y: number; zoom: number }; + audio: { + enabled: boolean; + stream?: MediaStream; + }; + video: { + enabled: boolean; + stream?: MediaStream; + }; + isHost: boolean; + joinedAt: Date; +} + +interface CollaborationConfig { + community: { + id: string; + servers: { + signaling: string; + sync: string; + turn: { urls: string[]; username: string; credential?: string }; + }; + }; + session: { + audioEnabled: boolean; + videoEnabled: boolean; + screenShareEnabled: boolean; + }; + fallback: { + enableWebSocket: boolean; + fallbackDelay: number; // ms + }; +} + +export class CollaborationManager extends EventEmitter { + private doc: Y.Doc; + private webrtcProvider: WebrtcProvider | null = null; + private websocketProvider: WebsocketProvider | null = null; + private signalingWs: WebSocket | null = null; + + private currentSession: CollaborationSession | null = null; + private localParticipant: Participant | null = null; + + private localAudioStream: MediaStream | null = null; + private localVideoStream: MediaStream | null = null; + + private peers: Map = new Map(); + private config: CollaborationConfig; + + private fallbackTimeout: NodeJS.Timeout | null = null; + private connectionState: 'disconnected' | 'connecting' | 'connected' = 'disconnected'; + + constructor(config: CollaborationConfig) { + super(); + this.config = config; + this.doc = new Y.Doc(); + } + + /** + * Start a new collaboration session (host) + */ + async startSession(options: { + projectId: string; + title: string; + description?: string; + isPublic: boolean; + maxParticipants: number; + audioEnabled?: boolean; + videoEnabled?: boolean; + }): Promise { + + const roomId = this.generateRoomId(); + const userId = await this.getUserId(); + const peerId = this.generatePeerId(); + + this.currentSession = { + id: crypto.randomUUID(), + roomId, + projectId: options.projectId, + isHost: true, + isPublic: options.isPublic, + title: options.title, + description: options.description, + maxParticipants: options.maxParticipants, + participants: new Map(), + createdAt: new Date() + }; + + // Initialize local media if enabled + if (options.audioEnabled || options.videoEnabled) { + await this.initializeLocalMedia( + options.audioEnabled || false, + options.videoEnabled || false + ); + } + + // Create local participant + this.localParticipant = { + peerId, + userId, + name: await this.getUserName(), + avatar: await this.getUserAvatar(), + color: this.generateUserColor(), + isHost: true, + audio: { + enabled: options.audioEnabled || false, + stream: this.localAudioStream || undefined + }, + video: { + enabled: options.videoEnabled || false, + stream: this.localVideoStream || undefined + }, + joinedAt: new Date() + }; + + this.currentSession.participants.set(peerId, this.localParticipant); + + // Connect to signaling server + await this.connectToSignaling(roomId, peerId, userId); + + // Initialize WebRTC provider + await this.initializeWebRTC(roomId); + + // Publish session if public + if (options.isPublic) { + await this.publishSession(this.currentSession); + } + + this.emit('session-started', this.currentSession); + + return this.currentSession; + } + + /** + * Join an existing session + */ + async joinSession(options: { + roomId: string; + audioEnabled?: boolean; + videoEnabled?: boolean; + }): Promise { + + const userId = await this.getUserId(); + const peerId = this.generatePeerId(); + + // Initialize local media if enabled + if (options.audioEnabled || options.videoEnabled) { + await this.initializeLocalMedia( + options.audioEnabled || false, + options.videoEnabled || false + ); + } + + // Create local participant + this.localParticipant = { + peerId, + userId, + name: await this.getUserName(), + avatar: await this.getUserAvatar(), + color: this.generateUserColor(), + isHost: false, + audio: { + enabled: options.audioEnabled || false, + stream: this.localAudioStream || undefined + }, + video: { + enabled: options.videoEnabled || false, + stream: this.localVideoStream || undefined + }, + joinedAt: new Date() + }; + + // Connect to signaling server + await this.connectToSignaling(options.roomId, peerId, userId); + + // Initialize WebRTC provider + await this.initializeWebRTC(options.roomId); + + // Session details will be synced via Yjs + this.currentSession = { + id: crypto.randomUUID(), + roomId: options.roomId, + projectId: '', // Will be synced + isHost: false, + isPublic: false, // Will be synced + title: '', // Will be synced + maxParticipants: 10, // Will be synced + participants: new Map(), + createdAt: new Date() + }; + + this.currentSession.participants.set(peerId, this.localParticipant); + + this.emit('session-joined', this.currentSession); + + return this.currentSession; + } + + /** + * Leave current session + */ + async leaveSession() { + if (!this.currentSession) return; + + // Disconnect from signaling + if (this.signalingWs) { + this.signalingWs.send(JSON.stringify({ + type: 'leave', + room: this.currentSession.roomId, + peerId: this.localParticipant?.peerId + })); + this.signalingWs.close(); + this.signalingWs = null; + } + + // Close all peer connections + this.peers.forEach(peer => peer.destroy()); + this.peers.clear(); + + // Disconnect providers + if (this.webrtcProvider) { + this.webrtcProvider.destroy(); + this.webrtcProvider = null; + } + + if (this.websocketProvider) { + this.websocketProvider.destroy(); + this.websocketProvider = null; + } + + // Stop local media + this.stopLocalMedia(); + + // Unpublish if public and host + if (this.currentSession.isPublic && this.currentSession.isHost) { + await this.unpublishSession(this.currentSession.id); + } + + const sessionId = this.currentSession.id; + this.currentSession = null; + this.localParticipant = null; + + this.emit('session-left', sessionId); + } + + /** + * Connect to signaling server + */ + private async connectToSignaling(roomId: string, peerId: string, userId: string): Promise { + return new Promise((resolve, reject) => { + const signalingUrl = this.config.community.servers.signaling; + this.signalingWs = new WebSocket(signalingUrl); + + this.signalingWs.onopen = () => { + this.signalingWs!.send(JSON.stringify({ + type: 'join', + room: roomId, + peerId, + userId, + metadata: { + name: this.localParticipant?.name, + avatar: this.localParticipant?.avatar, + color: this.localParticipant?.color + } + })); + }; + + this.signalingWs.onmessage = (event) => { + const msg = JSON.parse(event.data); + this.handleSignalingMessage(msg); + + if (msg.type === 'joined') { + resolve(); + } + }; + + this.signalingWs.onerror = (error) => { + reject(error); + }; + }); + } + + /** + * Handle messages from signaling server + */ + private handleSignalingMessage(msg: any) { + switch (msg.type) { + case 'joined': + // Successfully joined room, existing peers listed + msg.peers.forEach((peer: any) => { + this.initiatePeerConnection(peer.peerId, true, peer); + }); + break; + + case 'peer-joined': + // New peer joined, wait for them to initiate + const participant: Participant = { + peerId: msg.peerId, + userId: msg.userId, + name: msg.metadata.name, + avatar: msg.metadata.avatar, + color: msg.metadata.color, + isHost: false, + audio: { enabled: false }, + video: { enabled: false }, + joinedAt: new Date() + }; + this.currentSession?.participants.set(msg.peerId, participant); + this.emit('participant-joined', participant); + break; + + case 'peer-left': + // Peer left + const peer = this.peers.get(msg.peerId); + if (peer) { + peer.destroy(); + this.peers.delete(msg.peerId); + } + this.currentSession?.participants.delete(msg.peerId); + this.emit('participant-left', msg.peerId); + break; + + case 'signal': + // WebRTC signal from peer + const existingPeer = this.peers.get(msg.from); + if (existingPeer) { + existingPeer.signal(msg.signal); + } else { + // Peer doesn't exist yet, they're initiating + this.initiatePeerConnection(msg.from, false); + setTimeout(() => { + this.peers.get(msg.from)?.signal(msg.signal); + }, 100); + } + break; + + case 'invite': + // Received collaboration invite + this.emit('invite-received', { + from: msg.from, + roomId: msg.roomId, + sessionInfo: msg.sessionInfo + }); + break; + } + } + + /** + * Initialize WebRTC peer connection + */ + private initiatePeerConnection(peerId: string, initiator: boolean, metadata?: any) { + const peer = new SimplePeer({ + initiator, + trickle: true, + config: { + iceServers: [ + { urls: 'stun:stun.l.google.com:19302' }, + ...this.config.community.servers.turn.urls.map(url => ({ + urls: url, + username: this.config.community.servers.turn.username, + credential: this.config.community.servers.turn.credential + })) + ] + }, + stream: this.combineMediaStreams() + }); + + peer.on('signal', (signal) => { + // Send signal via signaling server + this.signalingWs?.send(JSON.stringify({ + type: 'signal', + to: peerId, + signal + })); + }); + + peer.on('stream', (stream) => { + // Received remote stream + const participant = this.currentSession?.participants.get(peerId); + if (participant) { + // Determine if audio or video track + const audioTrack = stream.getAudioTracks()[0]; + const videoTrack = stream.getVideoTracks()[0]; + + if (audioTrack) { + participant.audio.stream = new MediaStream([audioTrack]); + } + if (videoTrack) { + participant.video.stream = new MediaStream([videoTrack]); + } + + this.emit('participant-updated', participant); + } + }); + + peer.on('connect', () => { + this.emit('peer-connected', peerId); + }); + + peer.on('close', () => { + this.peers.delete(peerId); + this.emit('peer-disconnected', peerId); + }); + + peer.on('error', (err) => { + console.error(`Peer ${peerId} error:`, err); + }); + + this.peers.set(peerId, peer); + } + + /** + * Initialize WebRTC provider (Yjs) + */ + private async initializeWebRTC(roomId: string) { + this.webrtcProvider = new WebrtcProvider(roomId, this.doc, { + signaling: [this.config.community.servers.signaling], + maxConns: this.currentSession?.maxParticipants || 10, + filterBcConns: true, + peerOpts: { + config: { + iceServers: [ + { urls: 'stun:stun.l.google.com:19302' }, + ...this.config.community.servers.turn.urls.map(url => ({ + urls: url, + username: this.config.community.servers.turn.username, + credential: this.config.community.servers.turn.credential + })) + ] + } + } + }); + + // Setup awareness (cursors, selections, viewport) + this.setupAwareness(); + + // Setup fallback timer + if (this.config.fallback.enableWebSocket) { + this.fallbackTimeout = setTimeout(() => { + if (this.webrtcProvider!.connected === false) { + console.log('WebRTC connection timeout, falling back to WebSocket'); + this.initializeWebSocketFallback(roomId); + } + }, this.config.fallback.fallbackDelay); + } + + this.webrtcProvider.on('status', ({ connected }) => { + if (connected) { + this.connectionState = 'connected'; + if (this.fallbackTimeout) { + clearTimeout(this.fallbackTimeout); + this.fallbackTimeout = null; + } + this.emit('connection-established', 'webrtc'); + } + }); + + this.webrtcProvider.on('peers', ({ webrtcPeers, bcPeers }) => { + this.emit('peers-changed', { + webrtc: webrtcPeers, + broadcast: bcPeers + }); + }); + + // Sync project data to Yjs doc + this.syncProjectToYDoc(); + } + + /** + * Initialize WebSocket fallback + */ + private initializeWebSocketFallback(roomId: string) { + this.websocketProvider = new WebsocketProvider( + this.config.community.servers.sync, + roomId, + this.doc, + { connect: true } + ); + + this.websocketProvider.on('status', ({ status }) => { + if (status === 'connected') { + this.connectionState = 'connected'; + this.emit('connection-established', 'websocket'); + } + }); + } + + /** + * Setup awareness for cursor/selection sharing + */ + private setupAwareness() { + if (!this.webrtcProvider) return; + + const awareness = this.webrtcProvider.awareness; + + // Set local state + awareness.setLocalState({ + user: { + peerId: this.localParticipant?.peerId, + userId: this.localParticipant?.userId, + name: this.localParticipant?.name, + color: this.localParticipant?.color, + avatar: this.localParticipant?.avatar + }, + cursor: null, + selection: null, + viewport: null + }); + + // Listen for remote changes + awareness.on('change', () => { + this.renderRemoteAwareness(); + }); + } + + /** + * Render remote cursors, selections, viewports + */ + private renderRemoteAwareness() { + if (!this.webrtcProvider) return; + + const awareness = this.webrtcProvider.awareness; + const states = awareness.getStates(); + + const remoteCursors: any[] = []; + const remoteSelections: any[] = []; + const remoteViewports: any[] = []; + + states.forEach((state, clientId) => { + if (clientId === this.doc.clientID) return; // Skip self + + if (state.cursor) { + remoteCursors.push({ + user: state.user, + cursor: state.cursor + }); + } + + if (state.selection) { + remoteSelections.push({ + user: state.user, + selection: state.selection + }); + } + + if (state.viewport) { + remoteViewports.push({ + user: state.user, + viewport: state.viewport + }); + } + }); + + this.emit('awareness-updated', { + cursors: remoteCursors, + selections: remoteSelections, + viewports: remoteViewports + }); + } + + /** + * Update local cursor position + */ + updateCursor(x: number, y: number) { + if (!this.webrtcProvider) return; + + const awareness = this.webrtcProvider.awareness; + awareness.setLocalStateField('cursor', { x, y }); + } + + /** + * Update local selection + */ + updateSelection(nodeId: string | null) { + if (!this.webrtcProvider) return; + + const awareness = this.webrtcProvider.awareness; + awareness.setLocalStateField('selection', nodeId ? { nodeId } : null); + } + + /** + * Update local viewport + */ + updateViewport(x: number, y: number, zoom: number) { + if (!this.webrtcProvider) return; + + const awareness = this.webrtcProvider.awareness; + awareness.setLocalStateField('viewport', { x, y, zoom }); + } + + /** + * Sync project data to Yjs document + */ + private syncProjectToYDoc() { + const yNodes = this.doc.getArray('nodes'); + const yConnections = this.doc.getArray('connections'); + const yProperties = this.doc.getMap('properties'); + + // Get current project data + const project = window.ProjectModel?.toJSON(); + + if (project) { + // Sync nodes + project.nodes?.forEach((node: any) => { + yNodes.push([node]); + }); + + // Sync connections + project.connections?.forEach((conn: any) => { + yConnections.push([conn]); + }); + + // Sync global properties + Object.entries(project.properties || {}).forEach(([key, value]) => { + yProperties.set(key, value); + }); + } + + // Listen for remote changes + yNodes.observe((event) => { + this.applyRemoteNodeChanges(event); + }); + + yConnections.observe((event) => { + this.applyRemoteConnectionChanges(event); + }); + + yProperties.observe((event) => { + this.applyRemotePropertyChanges(event); + }); + } + + /** + * Apply remote node changes to project + */ + private applyRemoteNodeChanges(event: any) { + event.changes.added.forEach((item: any) => { + const node = item.content.getContent()[0]; + // Add node to project (via ProjectModel) + window.ProjectModel?.addNode(node); + }); + + event.changes.deleted.forEach((item: any) => { + const nodeId = item.content.getContent()[0].id; + // Remove node from project + window.ProjectModel?.removeNode(nodeId); + }); + + this.emit('project-updated', 'nodes'); + } + + /** + * Apply remote connection changes + */ + private applyRemoteConnectionChanges(event: any) { + // Similar to nodes + this.emit('project-updated', 'connections'); + } + + /** + * Apply remote property changes + */ + private applyRemotePropertyChanges(event: any) { + // Similar to nodes + this.emit('project-updated', 'properties'); + } + + /** + * Local node changed - sync to Yjs + */ + onLocalNodeChanged(node: any) { + if (!this.doc) return; + + const yNodes = this.doc.getArray('nodes'); + + // Find and update node + const existingIndex = yNodes.toArray().findIndex((n: any) => n.id === node.id); + + if (existingIndex !== -1) { + yNodes.delete(existingIndex, 1); + yNodes.insert(existingIndex, [node]); + } else { + yNodes.push([node]); + } + } + + /** + * Initialize local media (audio/video) + */ + private async initializeLocalMedia(audio: boolean, video: boolean) { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + audio: audio ? { echoCancellation: true, noiseSuppression: true } : false, + video: video ? { width: 1280, height: 720 } : false + }); + + const audioTracks = stream.getAudioTracks(); + const videoTracks = stream.getVideoTracks(); + + if (audioTracks.length > 0) { + this.localAudioStream = new MediaStream(audioTracks); + } + + if (videoTracks.length > 0) { + this.localVideoStream = new MediaStream(videoTracks); + } + + this.emit('local-media-initialized', { + audio: !!this.localAudioStream, + video: !!this.localVideoStream + }); + + } catch (err) { + console.error('Failed to initialize media:', err); + throw err; + } + } + + /** + * Combine audio and video streams + */ + private combineMediaStreams(): MediaStream | undefined { + const combinedTracks: MediaStreamTrack[] = []; + + if (this.localAudioStream) { + combinedTracks.push(...this.localAudioStream.getAudioTracks()); + } + + if (this.localVideoStream) { + combinedTracks.push(...this.localVideoStream.getVideoTracks()); + } + + return combinedTracks.length > 0 ? new MediaStream(combinedTracks) : undefined; + } + + /** + * Stop local media + */ + private stopLocalMedia() { + if (this.localAudioStream) { + this.localAudioStream.getTracks().forEach(track => track.stop()); + this.localAudioStream = null; + } + + if (this.localVideoStream) { + this.localVideoStream.getTracks().forEach(track => track.stop()); + this.localVideoStream = null; + } + } + + /** + * Toggle local audio + */ + toggleAudio(enabled?: boolean) { + if (!this.localAudioStream) return; + + const audioTrack = this.localAudioStream.getAudioTracks()[0]; + if (audioTrack) { + audioTrack.enabled = enabled !== undefined ? enabled : !audioTrack.enabled; + if (this.localParticipant) { + this.localParticipant.audio.enabled = audioTrack.enabled; + } + this.emit('audio-toggled', audioTrack.enabled); + } + } + + /** + * Toggle local video + */ + toggleVideo(enabled?: boolean) { + if (!this.localVideoStream) return; + + const videoTrack = this.localVideoStream.getVideoTracks()[0]; + if (videoTrack) { + videoTrack.enabled = enabled !== undefined ? enabled : !videoTrack.enabled; + if (this.localParticipant) { + this.localParticipant.video.enabled = videoTrack.enabled; + } + this.emit('video-toggled', videoTrack.enabled); + } + } + + /** + * Invite user to session + */ + async inviteUser(targetUserId: string) { + if (!this.currentSession || !this.signalingWs) return; + + this.signalingWs.send(JSON.stringify({ + type: 'invite', + targetUserId, + roomId: this.currentSession.roomId, + sessionInfo: { + title: this.currentSession.title, + description: this.currentSession.description, + hostName: this.localParticipant?.name + } + })); + } + + /** + * Publish public session + */ + private async publishSession(session: CollaborationSession) { + // Add to community's collaboration/public-sessions.json via GitHub API + const community = this.config.community; + + // This would use Octokit to update the file + // Implementation depends on GitHub OAuth setup + } + + /** + * Unpublish session + */ + private async unpublishSession(sessionId: string) { + // Remove from community's public-sessions.json + } + + // Utility methods + private generateRoomId(): string { + return `nodegx-${crypto.randomUUID()}`; + } + + private generatePeerId(): string { + return `peer-${crypto.randomUUID()}`; + } + + private generateUserColor(): string { + const colors = [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', + '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2' + ]; + return colors[Math.floor(Math.random() * colors.length)]; + } + + private async getUserId(): Promise { + // Get from GitHub OAuth + return window.githubAuth?.user?.login || 'anonymous'; + } + + private async getUserName(): Promise { + return window.githubAuth?.user?.name || 'Anonymous'; + } + + private async getUserAvatar(): Promise { + return window.githubAuth?.user?.avatar_url; + } + + destroy() { + this.leaveSession(); + } +} +``` + +### Implementation Tasks + +- [ ] Install WebRTC dependencies (y-webrtc, y-websocket, simple-peer) +- [ ] Implement `CollaborationManager` service +- [ ] Implement session creation (host) +- [ ] Implement session joining (guest) +- [ ] Implement WebRTC peer connections +- [ ] Implement signaling server communication +- [ ] Implement Yjs document synchronization +- [ ] Implement awareness (cursors, selections, viewports) +- [ ] Implement audio/video media handling +- [ ] Implement WebSocket fallback logic +- [ ] Implement user invitations +- [ ] Add public session publishing to GitHub +- [ ] Add connection state management +- [ ] Add error handling and recovery + +### Verification Steps + +- [ ] Can start a collaboration session +- [ ] Can join a session via room ID +- [ ] WebRTC peers connect automatically +- [ ] Cursor positions sync in real-time +- [ ] Node changes sync across all peers +- [ ] Audio chat works between peers +- [ ] Falls back to WebSocket when WebRTC fails +- [ ] Can invite users to session +- [ ] Can toggle audio/video +- [ ] Disconnections handled gracefully + +### Notes + +- WebRTC connections are peer-to-peer (no server relay) +- Signaling server only helps with initial connection +- TURN server used as fallback when P2P impossible +- Yjs handles conflict resolution automatically +- Awareness updates are lightweight (not stored in doc) + +--- + +## GIT-8: Notification System + +**Priority**: High +**Estimated Hours**: 50-70 +**Dependencies**: GIT-6 (Notification Server) + +### Purpose + +Implement persistent cross-session notification system for collaboration invites, community events, mentions, and updates. + +### Technical Requirements + +**File: `packages/noodl-editor/src/editor/src/services/NotificationManager.ts`** + +```typescript +import EventEmitter from 'events'; + +interface Notification { + id: string; + userId: string; + fromUserId?: string; + type: 'invite' | 'mention' | 'thread' | 'session' | 'component' | 'system'; + title: string; + message: string; + data?: any; + read: boolean; + createdAt: Date; + expiresAt: Date; + readAt?: Date; + actions?: NotificationAction[]; +} + +interface NotificationAction { + label: string; + action: string; + primary?: boolean; +} + +export class NotificationManager extends EventEmitter { + private ws: WebSocket | null = null; + private notifications: Map = new Map(); + private userId: string | null = null; + private notificationServerUrl: string; + private reconnectInterval: NodeJS.Timeout | null = null; + private isConnected: boolean = false; + + constructor(notificationServerUrl: string) { + super(); + this.notificationServerUrl = notificationServerUrl; + } + + async initialize(userId: string) { + this.userId = userId; + await this.connect(); + await this.loadNotifications(); + } + + private async connect(): Promise { + return new Promise((resolve, reject) => { + this.ws = new WebSocket(this.notificationServerUrl); + + this.ws.onopen = () => { + this.isConnected = true; + + // Authenticate + this.ws!.send(JSON.stringify({ + type: 'authenticate', + userId: this.userId + })); + + this.emit('connected'); + }; + + this.ws.onmessage = (event) => { + const msg = JSON.parse(event.data); + this.handleMessage(msg); + + if (msg.type === 'authenticated') { + resolve(); + } + }; + + this.ws.onerror = (error) => { + console.error('Notification WebSocket error:', error); + reject(error); + }; + + this.ws.onclose = () => { + this.isConnected = false; + this.emit('disconnected'); + + // Auto-reconnect + this.scheduleReconnect(); + }; + }); + } + + private scheduleReconnect() { + if (this.reconnectInterval) return; + + this.reconnectInterval = setTimeout(async () => { + this.reconnectInterval = null; + try { + await this.connect(); + await this.loadNotifications(); + } catch (err) { + console.error('Reconnection failed:', err); + this.scheduleReconnect(); + } + }, 5000); + } + + private handleMessage(msg: any) { + switch (msg.type) { + case 'authenticated': + // Connection established + break; + + case 'notifications': + // Initial notification list + msg.notifications.forEach((n: any) => { + const notification = this.parseNotification(n); + this.notifications.set(notification.id, notification); + }); + this.emit('notifications-loaded', Array.from(this.notifications.values())); + break; + + case 'notification': + // New notification + const notification = this.parseNotification(msg.notification); + this.notifications.set(notification.id, notification); + this.emit('notification-received', notification); + + // Show toast + this.showToast(notification); + break; + + case 'notification-updated': + // Notification marked read + const updated = this.parseNotification(msg.notification); + this.notifications.set(updated.id, updated); + this.emit('notification-updated', updated); + break; + + case 'notification-deleted': + // Notification deleted + this.notifications.delete(msg.notificationId); + this.emit('notification-deleted', msg.notificationId); + break; + } + } + + private parseNotification(data: any): Notification { + return { + id: data.id, + userId: data.userId, + fromUserId: data.fromUserId, + type: data.type, + title: this.getNotificationTitle(data), + message: this.getNotificationMessage(data), + data: data.data, + read: data.read, + createdAt: new Date(data.createdAt), + expiresAt: new Date(data.expiresAt), + readAt: data.readAt ? new Date(data.readAt) : undefined, + actions: this.getNotificationActions(data) + }; + } + + private getNotificationTitle(data: any): string { + switch (data.type) { + case 'invite': + return 'Collaboration Invite'; + case 'mention': + return 'Someone mentioned you'; + case 'thread': + return 'New discussion thread'; + case 'session': + return 'Public session available'; + case 'component': + return 'New component available'; + case 'system': + return 'System notification'; + default: + return 'Notification'; + } + } + + private getNotificationMessage(data: any): string { + switch (data.type) { + case 'invite': + return `${data.data.from} invited you to collaborate on "${data.data.sessionTitle}"`; + case 'mention': + return `${data.data.from} mentioned you in ${data.data.location}`; + case 'thread': + return `New thread in ${data.data.community}: "${data.data.threadTitle}"`; + case 'session': + return `${data.data.host} started a public session: "${data.data.sessionTitle}"`; + case 'component': + return `${data.data.author} published "${data.data.componentName}"`; + case 'system': + return data.data.message; + default: + return JSON.stringify(data.data); + } + } + + private getNotificationActions(data: any): NotificationAction[] { + switch (data.type) { + case 'invite': + return [ + { label: 'Join', action: 'join', primary: true }, + { label: 'Decline', action: 'decline' } + ]; + case 'session': + return [ + { label: 'Join Session', action: 'join', primary: true } + ]; + case 'thread': + return [ + { label: 'View Thread', action: 'view', primary: true } + ]; + case 'component': + return [ + { label: 'View Component', action: 'view', primary: true } + ]; + default: + return []; + } + } + + private async loadNotifications() { + if (!this.ws || !this.isConnected) return; + + this.ws.send(JSON.stringify({ + type: 'get-notifications' + })); + } + + async sendNotification(targetUserId: string, type: Notification['type'], data: any) { + if (!this.ws || !this.isConnected) { + throw new Error('Not connected to notification server'); + } + + return new Promise((resolve, reject) => { + const handler = (msg: any) => { + if (msg.type === 'notification-sent') { + this.ws!.removeEventListener('message', handler); + resolve(); + } + }; + + this.ws!.addEventListener('message', (event) => { + handler(JSON.parse(event.data)); + }); + + this.ws.send(JSON.stringify({ + type: 'send-notification', + targetUserId, + type, + data + })); + }); + } + + async markAsRead(notificationId: string) { + if (!this.ws || !this.isConnected) return; + + this.ws.send(JSON.stringify({ + type: 'mark-read', + notificationId + })); + } + + async deleteNotification(notificationId: string) { + if (!this.ws || !this.isConnected) return; + + this.ws.send(JSON.stringify({ + type: 'delete-notification', + notificationId + })); + } + + getNotifications(): Notification[] { + return Array.from(this.notifications.values()) + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + } + + getUnreadCount(): number { + return Array.from(this.notifications.values()) + .filter(n => !n.read).length; + } + + private showToast(notification: Notification) { + // Emit event for UI to show toast + this.emit('show-toast', notification); + } + + destroy() { + if (this.reconnectInterval) { + clearTimeout(this.reconnectInterval); + } + + if (this.ws) { + this.ws.close(); + this.ws = null; + } + } +} +``` + +**File: `packages/noodl-editor/src/editor/src/components/NotificationToast.tsx`** + +```typescript +import React, { useEffect, useState } from 'react'; +import { Notification } from '../services/NotificationManager'; + +interface NotificationToastProps { + notification: Notification; + onAction: (action: string) => void; + onDismiss: () => void; +} + +export function NotificationToast({ + notification, + onAction, + onDismiss +}: NotificationToastProps) { + const [isVisible, = useState(true); + + useEffect(() => { + // Auto-dismiss after 10 seconds + const timeout = setTimeout(() => { + onDismiss(); + }, 10000); + + return () => clearTimeout(timeout); + }, [onDismiss]); + + if (!isVisible) return null; + + return ( +
+
+ {getIconForType(notification.type)} + {notification.title} + +
+ +
+

{notification.message}

+
+ + {notification.actions && notification.actions.length > 0 && ( +
+ {notification.actions.map(action => ( + + ))} +
+ )} +
+ ); +} + +function getIconForType(type: Notification['type']): string { + switch (type) { + case 'invite': return '👥'; + case 'mention': return '@'; + case 'thread': return '💬'; + case 'session': return '🎮'; + case 'component': return '📦'; + case 'system': return 'ℹ️'; + default: return '🔔'; + } +} +``` + +### Implementation Tasks + +- [ ] Implement `NotificationManager` service +- [ ] Connect to notification server on app start +- [ ] Implement notification parsing and storage +- [ ] Implement toast notification UI component +- [ ] Add notification badge to launcher/editor +- [ ] Add notification center panel +- [ ] Implement mark as read functionality +- [ ] Implement delete functionality +- [ ] Add notification actions (join, view, etc.) +- [ ] Persist notification state locally +- [ ] Add notification sound (optional) +- [ ] Add desktop notifications (Electron) + +### Verification Steps + +- [ ] Notifications persist across sessions +- [ ] Toast appears when notification received +- [ ] Can mark notifications as read +- [ ] Can delete notifications +- [ ] Unread count displays correctly +- [ ] Actions trigger correct behavior +- [ ] Desktop notifications work (Electron) +- [ ] Reconnects after connection loss + +--- + +*Due to character limits, I'll continue with GIT-9, GIT-10, and GIT-11 in a summary format. Would you like me to create a second file with the remaining tasks?* + +--- + +## Summary of Remaining Tasks + +### GIT-9: Community Tab UI/UX (80-100 hours) +- Browse communities (official + user-added) +- Discover public collaboration sessions +- Component library browser +- Tutorial and showcase feeds +- Job board integration +- Discussion thread viewer + +### GIT-10: Session Discovery & Joining (50-70 hours) +- Public session list +- Join via room ID/link +- Session details preview +- Quick join flow +- Session history +- Favorites/bookmarks + +### GIT-11: Integration & Polish (61-82 hours) +- End-to-end testing +- Performance optimization +- Documentation +- Demo videos +- Marketing materials +- Server deployment +- Monitor and analytics + +**Total Series Hours: 431-572 hours** diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Part-2-Community-UI-Integration.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Part-2-Community-UI-Integration.md new file mode 100644 index 0000000..db9e30c --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-002B-github-advanced-integration/GIT-5-to-GIT-11-Part-2-Community-UI-Integration.md @@ -0,0 +1,1691 @@ +# Live Collaboration & Multi-Community System - Part 2 +## Task Documentation: GIT-9 through GIT-11 + +This is a continuation of the Live Collaboration & Multi-Community System task documentation. + +--- + +## GIT-9: Community Tab UI/UX + +**Priority**: High +**Estimated Hours**: 80-100 +**Dependencies**: GIT-5, GIT-7, GIT-8 + +### Purpose + +Create the Community tab UI that serves as the central hub for all community features: browsing communities, discovering sessions, exploring components, viewing tutorials, and accessing discussions. + +### Technical Requirements + +#### 1. Community Tab Layout + +**File: `packages/noodl-editor/src/editor/src/views/panels/CommunityPanel/CommunityPanel.tsx`** + +```typescript +import React, { useState, useEffect } from 'react'; +import { CommunityManager } from '../../../services/CommunityManager'; +import { CollaborationManager } from '../../../services/CollaborationManager'; +import { NotificationManager } from '../../../services/NotificationManager'; + +interface CommunityPanelProps { + communityManager: CommunityManager; + collaborationManager: CollaborationManager; + notificationManager: NotificationManager; +} + +export function CommunityPanel({ + communityManager, + collaborationManager, + notificationManager +}: CommunityPanelProps) { + const [activeTab, setActiveTab] = useState<'home' | 'sessions' | 'components' | 'learn' | 'discuss' | 'jobs'>('home'); + const [activeCommunity, setActiveCommunity] = useState(communityManager.getActiveCommunity()); + const [communities, setCommunities] = useState(communityManager.getCommunities()); + + useEffect(() => { + const handleCommunityChanged = (community: any) => { + setActiveCommunity(community); + }; + + const handleCommunitiesUpdated = () => { + setCommunities(communityManager.getCommunities()); + }; + + communityManager.on('active-community-changed', handleCommunityChanged); + communityManager.on('community-added', handleCommunitiesUpdated); + communityManager.on('community-removed', handleCommunitiesUpdated); + + return () => { + communityManager.off('active-community-changed', handleCommunityChanged); + communityManager.off('community-added', handleCommunitiesUpdated); + communityManager.off('community-removed', handleCommunitiesUpdated); + }; + }, [communityManager]); + + return ( +
+ communityManager.setActiveCommunity(id)} + onAddCommunity={() => showAddCommunityDialog()} + /> + +
+ setActiveTab('home')} + /> + setActiveTab('sessions')} + /> + setActiveTab('components')} + /> + setActiveTab('learn')} + /> + setActiveTab('discuss')} + /> + setActiveTab('jobs')} + /> +
+ +
+ {activeTab === 'home' && ( + + )} + {activeTab === 'sessions' && ( + + )} + {activeTab === 'components' && ( + + )} + {activeTab === 'learn' && ( + + )} + {activeTab === 'discuss' && ( + + )} + {activeTab === 'jobs' && ( + + )} +
+
+ ); +} +``` + +#### 2. Home View (Featured Content) + +**File: `CommunityPanel/views/HomeView.tsx`** + +```typescript +import React, { useEffect, useState } from 'react'; + +export function HomeView({ community, collaborationManager, notificationManager }) { + const [featured, setFeatured] = useState({ + sessions: [], + components: [], + tutorials: [], + discussions: [] + }); + + useEffect(() => { + loadFeaturedContent(); + }, [community]); + + async function loadFeaturedContent() { + // Fetch from community repository + const response = await fetch( + `https://raw.githubusercontent.com/${getCommunityRepo()}/main/featured.json` + ); + const data = await response.json(); + setFeatured(data); + } + + return ( +
+ + +
+ joinSession(sessionId)} + /> +
+ +
+ +
+ +
+ +
+ +
+ +
+ + startNewSession()} + onShareComponent={() => shareComponent()} + onAskQuestion={() => startDiscussion()} + /> +
+ ); +} + +function WelcomeBanner({ community }) { + return ( +
+ {community.name} +

{community.name}

+

{community.description}

+
+ + + + +
+
+ ); +} + +function ActiveSessionsList({ sessions, onJoin }) { + if (sessions.length === 0) { + return ; + } + + return ( +
+ {sessions.map(session => ( + onJoin(session.id)} + /> + ))} +
+ ); +} + +function SessionCard({ session, onJoin }) { + return ( +
+
+
+ + {session.host.name} +
+ +
+ +

{session.title}

+

{session.description}

+ +
+ 👥 {session.participants.length}/{session.maxParticipants} + 🕐 {formatDuration(session.duration)} + {session.audioEnabled && 🎤 Audio} + {session.videoEnabled && 📹 Video} +
+ + +
+ ); +} + +function QuickActions({ onStartSession, onShareComponent, onAskQuestion }) { + return ( +
+ + + +
+ ); +} +``` + +#### 3. Sessions View (Collaboration Discovery) + +**File: `CommunityPanel/views/SessionsView.tsx`** + +```typescript +import React, { useState, useEffect } from 'react'; + +export function SessionsView({ community, collaborationManager }) { + const [view, setView] = useState<'browse' | 'create' | 'join'>('browse'); + const [sessions, setSessions] = useState([]); + const [filter, setFilter] = useState({ + status: 'all', // 'live', 'scheduled', 'all' + hasAudio: false, + hasSlots: false + }); + + useEffect(() => { + loadSessions(); + }, [community, filter]); + + async function loadSessions() { + // Fetch from community's collaboration/public-sessions.json + const response = await fetch( + `https://raw.githubusercontent.com/${getCommunityRepo()}/main/collaboration/public-sessions.json` + ); + const data = await response.json(); + setSessions(filterSessions(data, filter)); + } + + return ( +
+
+

Collaboration Sessions

+
+ + +
+
+ + {view === 'browse' && ( + <> + + +
+ {sessions.length === 0 ? ( + setView('create') + }} + /> + ) : ( + sessions.map(session => ( + joinSession(session.roomId)} + /> + )) + )} +
+ + )} + + {view === 'create' && ( + setView('browse')} + onCreate={(session) => { + createSession(session); + setView('browse'); + }} + /> + )} + + {view === 'join' && ( + setView('browse')} + onJoin={(roomId) => { + joinSession(roomId); + setView('browse'); + }} + /> + )} +
+ ); +} + +function SessionFilters({ filter, onChange }) { + return ( +
+ + onChange({ ...filter, status: 'all' })} + /> + onChange({ ...filter, status: 'live' })} + /> + onChange({ ...filter, status: 'scheduled' })} + /> + + + + onChange({ ...filter, hasAudio: checked })} + /> + onChange({ ...filter, hasSlots: checked })} + /> + +
+ ); +} + +function CreateSessionDialog({ onCancel, onCreate }) { + const [form, setForm] = useState({ + title: '', + description: '', + isPublic: true, + maxParticipants: 10, + audioEnabled: true, + videoEnabled: false, + projectId: window.ProjectModel?.id || '' + }); + + return ( + +
+ setForm({ ...form, title })} + placeholder="e.g., Building a User Dashboard" + required + /> + +