feat: peer-to-peer session sharing via Syncthing#45
Open
JayantDevkar wants to merge 289 commits intomainfrom
Open
feat: peer-to-peer session sharing via Syncthing#45JayantDevkar wants to merge 289 commits intomainfrom
JayantDevkar wants to merge 289 commits intomainfrom
Conversation
47dbda4 to
9f85491
Compare
Confirms that Session.from_path() with remote JSONL paths correctly resolves subagent directories via the standard session_dir layout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add initial_prompt_images to SubagentSessionDetail schema and collectors so subagent overview pages show image attachments - Remove isMainSession gate in ConversationOverview so both session and subagent views render images consistently - Detect base64 image data in tool result content (ToolCallDetail) and render as inline images instead of raw text - Add binary content placeholder for non-image base64 data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Achieve full parity between local and remote sessions by packaging
missing resource types in the CLI and verifying all API path resolution
works end-to-end for synced remote sessions.
## CLI Packager (cli/karma/packager.py)
- Package file-history directories (~/.claude/file-history/{uuid}/) into
staging for sync
- Package debug log files (~/.claude/debug/{uuid}.txt) into staging
## API Bug Fix (api/services/session_lookup.py)
- Fix early `return None` in find_session_with_project() when
projects_dir doesn't exist — was blocking the remote session fallback
path entirely. Now skips local scan gracefully and falls through to
remote lookup.
## API Indexer (api/db/indexer.py)
- Add claude_base_dir parameter to _index_session() so remote sessions
resolve resource paths (todos, tasks, file-history, debug) correctly
during indexing instead of defaulting to ~/.claude
## Tests (72 new/modified tests, all passing)
### CLI Tests (cli/tests/test_packager.py) — 6 new
- TestFileHistorySyncing: copies file-history, skips missing, idempotent
- TestDebugLogSyncing: copies debug logs, skips missing, worktree support
### API Unit Tests (api/tests/test_remote_sessions.py) — 6 new
- TestRemoteSessionTodos: todos resolve via claude_base_dir
- TestRemoteSessionTasks: tasks resolve via claude_base_dir
- TestRemoteSessionToolResults: tool-results resolve via session_dir
- TestRemoteSessionFileHistory: file-history dir resolves
- TestRemoteSessionDebugLog: debug logs resolve and are readable
- TestRemoteSessionMissingResources: graceful empty returns
### API Integration Tests (api/tests/api/test_remote_sessions.py) — 8 new
- Endpoint tests for GET /sessions/{uuid}, /todos, /tasks, /subagents,
/timeline, /file-activity, /tools, and 404 handling via remote fallback
### E2E Roundtrip Test (api/tests/test_remote_roundtrip.py) — 3 new
- Full pipeline: find_remote_session → verify all resources →
index_remote_sessions → verify SQLite row
## Resource Parity Table (after this commit)
| Resource | Packaged | API resolves | Verified |
|--------------|----------|--------------|----------|
| JSONL | YES | YES | YES |
| Subagents | YES | YES | YES |
| Tool-results | YES | YES | YES |
| Todos | YES | YES | YES |
| Tasks | YES | YES | YES |
| File-history | YES (new)| YES | YES (new)|
| Debug logs | YES (new)| YES | YES (new)|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-history Code review of commit 8702c98 identified two simplification opportunities in the CLI packager's package() method: 1. Compute `_claude_base` (self.project_dir.parent.parent) once in __init__ instead of repeating it 4 times across resource-copying blocks. 2. Deduplicate tasks and file-history copying — both used identical copytree-by-uuid logic. Replaced with a single loop over ("tasks", "file-history") resource names. The 3 distinct copy strategies are now clearly separated: - Todos: glob pattern ({uuid}-*.json) → copy2 - Tasks + file-history: directory by uuid → copytree (deduplicated) - Debug logs: single file ({uuid}.txt) → copy2 All 72 tests pass (26 packager + 46 remote session). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sync_project() was not passing extra_dirs to SessionPackager, so worktree sessions were silently excluded from IPFS syncs. The Syncthing path (main.py) already handled this correctly. Now both sync backends discover worktree dirs via find_worktree_dirs() and flatten them into the same outbox, matching the Syncthing behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tabbed /sync page with 4 tabs: Setup (install/init/pair), Devices (full monitoring), Projects (toggle sync with progressive disclosure), Activity (live event feed + bandwidth chart). Replaces Syncthing's localhost UI entirely. MVP persona: solo user with multiple machines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: Syncthing proxy service, expanded /sync router with detect, devices, projects, activity, init, enable/disable endpoints. Frontend: tabbed /sync page (Setup, Devices, Projects, Activity), nav update, Chart.js bandwidth chart, progressive disclosure components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full-stack implementation of the Sync page that replaces Syncthing's localhost UI with an integrated dashboard inside Claude Karma. Backend (API): - SyncthingProxy service layer wrapping CLI's SyncthingClient with async support via run_in_executor - 11 endpoints: /sync/status, /sync/teams, /sync/detect, /sync/devices (GET/POST/DELETE), /sync/projects (GET + enable/disable/sync-now), /sync/activity, /sync/init - Input validation with regex allowlists for device IDs and project names - 46 tests covering all endpoints, proxy layer, and edge cases Frontend (SvelteKit/Svelte 5): - Setup tab: install detection, backend selection, init flow with machine name input, device ID display + copy - Devices tab: expandable device cards with connection status, transfer stats, encryption details - Projects tab: project list joined with sync folders, toggle sync per project, Select All, Sync Now actions - Activity tab: bandwidth sparkline chart (Chart.js) with live upload/download rates, event log with colored status dots - 10s polling with AbortController guard, URL-synced tab state - Sync nav link in Header (desktop + mobile), skeleton route Design docs updated with implementation amendments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ProjectsTab: fetch /sync/teams instead of /sync/projects for correct synced-project matching by encoded_name (Syncthing folder IDs differ) - SetupTab: destructure .devices from API response, filter out self device so "This Machine" doesn't duplicate under Paired Devices - sync_status.py: /sync/teams returns rich project objects (name, encoded_name, path); /sync/activity includes bandwidth stats - syncthing_proxy.py: add get_bandwidth() via /rest/system/connections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tals - Poll /sync/activity every 3s instead of one-shot fetch - Accumulate upload_rate/download_rate into history arrays client-side (API returns point-in-time values, not time series) - Display upload_total/download_total below the chart - Add FolderCompletion and ClusterConfigReceived event formatters - Merge new events incrementally on each poll (dedupe by event ID) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass timeout=1s to Syncthing /rest/events when since>0 so it returns immediately instead of blocking until new events arrive - Catch transient errors on events/bandwidth calls gracefully (return empty) while still propagating SyncthingNotRunning as 503 - Reduce requests timeout from 10s to 5s Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…stats Activity tab: - Resolve Syncthing folder IDs → readable names using getProjectNameFromEncoded - Resolve device IDs → device names via /sync/devices lookup - Show events newest-first (reverse chronological) - Load lookup maps on mount for enriching event display Devices tab: - Include in_bytes_total, out_bytes_total, type, crypto from Syncthing connections API (previously missing — showed 0 B for all devices) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…esolution Activity tab: - Add "Synced Folders" section showing per-folder stats from /rest/db/status (e.g., "3.1 GB, 4,145 files, 100%") — the actual transfer volumes - Reuse getProjectNameFromEncoded for folder name resolution (no more hacks) - Bandwidth section clarified: shows current session protocol traffic Devices: - Self device now shows aggregate transfer totals (not 0 B) - Self device reports connected=true and is_self=true from API - Remote devices include type/crypto from connections API Proxy: - get_folder_status() enriches folders with /rest/db/status (globalBytes, localBytes, needBytes, state, file counts) - get_devices() fetches raw connections response for total + per-device stats - Detects self device ID via /rest/system/status Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e scale Chart.js canvas cannot resolve CSS custom properties. Resolve --text-muted via getComputedStyle for tick labels. Add suggestedMax: 1024 to prevent degenerate 0-1 Y-axis when idle. Increase line visibility (borderWidth 2, fill alpha 0.15) and limit Y ticks to 4. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Connection-level inBytesTotal/outBytesTotal only show protocol overhead since last Syncthing restart (~4KB), not actual sync data. Replace with folder-level inSyncBytes aggregated by type: sendonly folders → "Synced out", receiveonly folders → "Synced in". Now shows real volumes (3.1 GB / 17.6 MB). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add rescan_folder() and rescan_all() to SyncthingProxy (POST /rest/db/scan)
- Wire /sync/rescan endpoint for global rescan, /sync/projects/{name}/sync-now
for per-project rescan with folder ID matching
- Add "Sync Now" button in Activity tab header (rescans all folders)
- Show "Sync Now" button on all synced projects in ProjectRow (not just pending)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Project display name "claude-karma" didn't substring-match Syncthing folder ID "karma-out-jay-claude-code-karma". Fix by: - Frontend sends encoded_name instead of display name for better matching - API matches against folder id, path, and label - Increase project name length limit to 512 for encoded paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_load_config() silently returned None when sync-config.json had trailing duplicate data (json.JSONDecodeError). This caused /sync/teams to return empty, making all projects show as "not synced" in the Projects tab. Add partial JSON recovery via json.JSONDecoder.raw_decode() as fallback when standard json.loads() fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… stats Backend (API): - Fix SyncthingProxy singleton never reconnecting after Syncthing starts late (_require_client retries _try_connect when client is None) - Fix detect() returning running:true when status API call fails - Consolidate dual config loaders (_load_config + _load_sync_config) into single _load_sync_config using CLI's SyncConfig.load() with error recovery - Centralize sys.path manipulation (was repeated 3 times, now once at module level) Frontend — shared types & utilities: - Add SyncDetect, SyncStatusResponse, SyncDevice, SyncProject to api-types.ts (removes 7 duplicate interface definitions across 4 files) - Add formatBytes() and formatBytesRate() to utils.ts with consistent 1024-based units (fixes inconsistency: ActivityTab used 1000-based, DeviceCard used 1024-based) Frontend — component fixes: - SetupTab: replace State 3 device list (~200 lines) with overview dashboard showing 4 stat cards (Devices, Synced Projects, Synced In, Synced Out) plus machine details. Eliminates full duplication of DevicesTab. - DevicesTab: now owns all device management (pair + remove with confirm), previously split between SetupTab and DevicesTab - ProjectsTab/ProjectRow: fix name vs encoded_name bug — handleToggle and selectAll were sending display name instead of encoded_name to API - ProjectRow: remove non-functional placeholder buttons (Files/Sync History) - ActivityTab/BandwidthChart/DeviceCard: replace local formatBytes and formatRelativeTime with shared utils Net: -149 lines, 1 critical bug fixed, 2 data bugs fixed, zero type errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- All tab components reload data on tab switch via active prop + $effect - DevicesTab shows flash messages on add/remove, auto-uppercases device ID - DeviceCard uses amber dot for disconnected, adds copy device ID button - ProjectsTab uses live Syncthing device count instead of stale team config - SetupTab conditionally renders version to avoid blank space - Duplicate device add returns HTTP 409 with SyncthingClient pre-check - Improved confirmation popover styling for device removal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add find_desktop_worktree_dirs() and find_all_worktree_dirs() to discover
Desktop worktrees (~/.claude-worktrees/) alongside CLI worktrees. Wire
into sync, watch, and status commands so all worktree sessions are
packaged for Syncthing sync.
- find_desktop_worktree_dirs: scans for -claude-worktrees-{project}- pattern
- find_all_worktree_dirs: combines CLI + Desktop discovery with dedup
- project_name_from_path: extracts dir name from full project path
- Watch command now monitors Desktop worktree dirs for changes
- Status command counts Desktop worktree sessions
- Diagnostic tests verify discovery works on real filesystem
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iews
Team Management:
- New TeamSelector component: team switching dropdown, inline stats,
"+ New Team" entry point between tab strip and content
- Active team context passed to Overview/Members/Projects tabs
- Team create/delete in OverviewTab with confirmation dialog
- Auto-select first team, react to team changes across all tabs
Projects Tab UX:
- Search/filter input for project list (type-to-filter, case-insensitive)
- Sort: synced projects first, then alphabetical
- Replace 12px green dot toggle with 16x16 checkbox (WCAG 2.5.8)
- Confirmation dialog when disabling sync (prevents accidental toggles)
- "Select All" renamed to "Enable All (N)" with inline confirmation
- Disambiguate duplicate project names with path subtitle
- Remove lossy decodePath — pass encoded_name directly to API
- Flash messages for all sync actions via onaction callback
Overview Tab UX:
- Human-readable pending folder names (parse karma-out-{user}-{project})
- Descriptive sync engine banner explaining what Start does
- Remove duplicate team creation form (single entry via TeamSelector)
- Unify ID terminology: "Your Name", "Machine", "Sync ID"
- Watch start sends { team: teamName } in POST body
Members Tab UX:
- Accept teamName prop instead of auto-selecting first team
- Unify terminology: "Device ID" → "Sync ID" everywhere
- Clearer helper text for onboarding ("Paste their Sync ID here")
- Reload data when team switches
Data Consistency:
- TeamSelector defensive counting (member_count ?? members.length)
fixes "0 members · 0 projects" display bug
- Jargon elimination: "Packaged sessions" → "Queued for sync",
gap warning points to Overview tab's sync engine
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Projects that only exist on teammates' machines (synced via Syncthing) were returning 404s. Now the project detail endpoint checks for remote sessions before giving up, the branches endpoint serves DB data for non-local projects, and display_name is derived from git_identity (e.g. "owner/repo" → "repo") in upsert_team_project and the indexer so remote-only projects show readable names instead of raw encoded paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After deleting remote sessions, remote-only projects survived in the projects table and title cache, causing them to keep appearing in the project listing. Now removes orphan project rows (no remaining sessions) and their corresponding title cache files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comprehensive design for sync feature rewrite with Pydantic domain models, repository pattern, and simplified 3-phase reconciliation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes from code review: - C1/C2: sync_projects PK changed from encoded_name to git_identity (universal cross-machine key), cascaded to subscriptions FK - C4: Added typed SyncEvent model with event types and detail structures - I1: Added session packaging integration with subscription gating - I2: Clarified FolderManager subscription-to-folder mapping - I3: Specified metadata folder structure (team.json, member states, removal signals) - I4: Member.remove() now allows ADDED → REMOVED transition - I5: Documented dissolve_team cascade behavior at domain level - I6: Added cleanup logic for member removal, project removal, and auto-leave - I7: Documented sync_removed_members purpose in v4 - S4: Added updated_at to sync_members - S6: Added direction-to-Syncthing folder type mapping table - S7: Added cross-team device unpair safety check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- N1: sync_events column renamed project_encoded_name → project_git_identity - N3: record_removal() now accepts member_tag for audit - N5: MetadataService.write_own_state() adds projects parameter - N6: FolderManager adds remove_outbox_folder() and cleanup_project_folders() - N7: Phase 1 reconciliation detects removed projects and declines subscriptions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Master plan with dependency graph, agent recommendations, and file map. Phase 1: Foundation (domain models + schema v19 + repositories) Phase 2: Infrastructure (Syncthing client + managers + pairing) Phase 3: Services (team, project, metadata, reconciliation) Phase 4: API (routers + cleanup + E2E test) Phases 1 and 2 can run in parallel. 26 tasks total across 4 phases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Immutable Team model with TeamStatus enum, add/remove member operations guarded by leader authorization, dissolve transition, AuthorizationError and InvalidTransitionError exception types. 16 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Immutable Member model with MemberStatus enum (ADDED/ACTIVE/REMOVED), member_tag computed property, from_member_tag classmethod, and activate/remove state machine with InvalidTransitionError guards. 12 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Immutable SharedProject model with SharedProjectStatus enum, derive_folder_suffix() function (strips .git, replaces / with -), folder_suffix computed property, and remove() state transition. encoded_name is Optional[str]. 14 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Immutable Subscription model with SubscriptionStatus and SyncDirection enums, full state machine (accept/pause/resume/decline) with InvalidTransitionError guards, and change_direction() gated on ACCEPTED status. 22 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
18-value SyncEventType enum + immutable SyncEvent model with optional subject_id and payload. TDD: test file written first, all 18 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SyncthingClient: pure HTTP wrapper for Syncthing REST API - DeviceManager: pair/unpair/connection status - FolderManager: outbox/inbox/device list/cleanup operations - PairingService: permanent pairing code encode/decode (base32) - 90 tests passing across 4 test files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… be <4 chars) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drops all v3 sync_* tables and recreates with v4 schema: - sync_projects PK is (team_name, git_identity) - sync_subscriptions references project_git_identity - sync_events uses project_git_identity column - sync_members has updated_at column - All indexes created Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Domain models: fixed field names to match spec (name, leader_device_id, leader_member_tag, member_tag, git_identity, project_git_identity) - Team.add_member/remove_member take Member objects (not device strings) - SyncEvent.team_name is Optional (not required) - All 5 repositories implemented with UPSERT save() - 225 tests passing across domain models, repos, schema, Syncthing, pairing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TeamService: create_team, add_member, remove_member, dissolve_team ProjectService: share_project, remove_project, accept/pause/resume/decline subscription, change_direction 39 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3-phase reconciliation (metadata → mesh-pair → device-lists) that runs every 60s. Detects removal signals, discovers new members, declines subs for removed projects, pairs active peers, and applies declarative device lists to Syncthing folders. 19 tests, all passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests full journey: create team → share project → pairing code → add member → accept subscription → change direction → remove member. 5 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sync_deps.py: shared dependency factories (conn, config, repos, services) - sync_teams.py: team CRUD + member management via TeamService - sync_projects.py: project sharing + subscription lifecycle via ProjectService - sync_pairing.py: NEW — pairing code generation/validation + device status - sync_system.py: simplified — status, init, detect, reset + new /reconcile - main.py: register v4 routers, remove v3 router imports - 44 router tests (all passing) with mocked services + real in-memory DB Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace 6-phase MetadataReconciliationTimer with ReconciliationTimer that calls ReconciliationService.run_cycle() (3-phase pipeline) - Gate session packaging by v4 subscriptions (send|both direction) - Log events via v4 EventRepository instead of v3 sync_queries - Remove _ConfigProxy/_SyncthingProxy (no longer needed) - Preserve H1 fix: dedicated SQLite connection per timer thread - Preserve 120s timeout on async reconciliation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tecture Delete 12 v3 service/router/db files: - routers: sync_members, sync_pending, sync_devices, sync_operations - services: sync_identity, sync_policy, sync_folders, sync_reconciliation, sync_metadata_reconciler, sync_metadata_writer, syncthing_proxy - db: sync_queries Delete 30 v3 test files (all tested deleted code). Fix 2 production files with lazy imports of deleted modules: - db/indexer.py: use v4 EventRepository + MemberRepository - services/remote_sessions.py: use v4 MemberRepository 313 v4 tests passing. No new regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- C1: add _assert_active() to Team.add_member/remove_member - C2: InvalidTransitionError now extends ValueError (per spec) - C3: catch InvalidTransitionError → 409 in all 8 router endpoints - C6: use SharedProjectStatus.SHARED enum instead of string comparison - I1: add dot validation in Member.from_member_tag - I5: move event log before team delete in dissolve_team - I8: add _validate_path_component() for path traversal prevention - Extend SyncthingClient._delete to accept params, simplify dismiss methods Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend (11 new endpoints): - sync_pending.py: pending devices (list/accept/dismiss) + folders (list/accept/reject) - sync_teams.py: join-code, activity, project-status, session-stats - SyncthingClient: get_pending_devices, get_pending_folders, dismiss methods Frontend (15 files): - api-types.ts: v4 types (SyncTeam, SyncTeamMember, SyncSubscription, etc.) - Server loaders: single-call team detail, simplified team listing - Components: TeamOverviewTab, TeamMembersTab, TeamProjectsTab (subscription management with accept/pause/resume/decline + direction picker), TeamActivityTab, CreateTeamDialog, JoinTeamDialog, AddProjectDialog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l API - syncing-sessions.md: add subscriptions concept (4th pillar), update team lifecycle (leader/dissolved), fix folder naming to member_tag + folder_suffix, update join flow with pairing codes, revise settings - api-reference.md: add 30+ sync endpoints across 5 sections (system, teams, projects/subscriptions, pending, pairing), add 403/409 status codes, add example responses - quick-start.md: replace CLI device-id flow with dashboard-first setup wizard and join code workflow - features.md: replace device-id joining with join codes, add subscription control section, remove CLI command examples - architecture.md: expand sync endpoint listing with v4 routes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8e66a04 to
55f00be
Compare
In v4, only the leader adds members by pasting their pairing code. There is no self-service "join team" action. - Remove JoinTeamDialog from team listing page - Remove JoinCodeCard from team overview tab - Remove join-code fetch from team detail server loader - Update member tab messaging to reflect v4 pairing flow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Leaders can now add teammates from the Members tab by pasting their pairing code. Includes inline form with error handling, loading state, and link to /sync where codes are generated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…riptions - Fix subscription field mismatch (API sent "project", frontend expected "project_git_identity") - Fix sync/status teams format from dict to array to match frontend types - Add prominent v4 pairing code card on /sync overview (replaces v3 join code) - Redesign /team empty state with dual-path: leader (create team) vs member (share pairing code) - Add GettingStartedBanner component for new team onboarding (3-step guide) - Add OFFERED subscription notification banner on team Projects tab - Replace v3 TeamSettings tab with clean Leave/Dissolve actions - Add pending subscription badge to TeamCard on team list page - Remove v3 dead code: JoinTeamDialog, JoinCodeCard, SessionLimitSelector, TeamSettings, join-code.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rter base32-encoding a payload containing a Syncthing device ID (already base32) produced 87-block codes (434 chars) — unusable for copy-paste sharing. Switching to base64url with 6-char blocks reduces codes to ~23 blocks (157 chars) for typical member_tags. Backwards-compatible: validate_code() auto-detects legacy base32 codes (all uppercase) vs new base64url codes. Also adds TestMemberGeneratesLeaderDecodes test class with 7 end-to-end tests covering the real v4 flow: member generates code → shares out-of-band → leader decodes on their machine. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
Share Claude Code sessions with your team — automatically, securely, with zero cloud infrastructure.
When you use Claude Code, your sessions live in
~/.claude/on your machine. That's great for solo work, but the moment you have two machines or a teammate, everyone's sessions are invisible to each other. You lose context, duplicate work, and can't learn from how others use Claude.This PR adds peer-to-peer session syncing powered by Syncthing. Sessions travel directly between machines over encrypted connections. No servers, no accounts, no third parties touching your data.
How it works
/sync— the setup wizard detects Syncthing and walks you through initializationFour core concepts
jayant.macbook). Same person on two machines = two members.org/repo). You choose what to share.What you can do
/teampage — add members via join codes, share projects, view activityWhat gets synced (and what doesn't)
Synced: session conversations, tool usage, token stats, subagent activity, session metadata
Never synced: your source code, secrets,
.envfiles, anything outside~/.claude/projects/, anything from projects you haven't sharedArchitecture
The sync system is built in clean layers:
Domain-driven design: frozen Pydantic models with explicit state machines. Teams are active or dissolved. Members are added, active, or removed. Subscriptions are offered, accepted, paused, or declined. Invalid transitions raise errors.
3-phase reconciliation: runs every 60 seconds in the background
What's in the PR
Backend —
api/api/domain/(5 files)api/db/schema.pyapi/repositories/(5 files)api/services/syncthing/(3 files)api/services/sync/(5 files)api/routers/sync_*.py(5 files)api/services/watcher_manager.pyFrontend —
frontend//syncpage/teampage/team/[name]api-types.tsupdated with SyncTeam, SyncTeamMember, SyncSubscription, SyncEventCLI —
cli/karma/sync-config.json)Documentation —
docs/about/Numbers
+65,615 / -2,037linesTest plan
svelte-check— 0 errors)🤖 Generated with Claude Code