The Stellar Dev Dashboard now includes real-time collaboration features that enable multi-user sessions across browser tabs without requiring a backend server. All collaboration features use the browser's BroadcastChannel API for cross-tab communication and IndexedDB for persistent storage.
Track which users/tabs are viewing the same accounts or transactions in real-time.
Components:
PresenceManager- Core presence tracking systemusePresence- React hook for accessing presence featuresPresenceIndicator- Compact viewer count displayPresenceList- Detailed viewer list with status
Features:
- Real-time viewer count per account
- Shows which tab each viewer is on
- Tracks cursor positions and selections
- Automatic cleanup of inactive users (15s timeout)
- Heartbeat mechanism (5s interval)
Usage:
import { usePresence } from '../hooks/usePresence'
import { PresenceIndicator } from '../components/collaboration/PresenceIndicator'
function MyComponent() {
const { users, updateAccount, updateActiveTab } = usePresence()
useEffect(() => {
updateAccount(accountId)
}, [accountId, updateAccount])
return <PresenceIndicator accountId={accountId} />
}Add comments and annotations to accounts, transactions, and contracts that persist across sessions.
Components:
AnnotationsStore- Annotation management with IndexedDB persistenceuseAnnotations- React hook for annotation operationsAnnotationsPanel- UI for viewing and managing annotations
Features:
- Add, edit, resolve, and delete annotations
- Annotations persist in IndexedDB
- Real-time sync across tabs via BroadcastChannel
- Type-specific annotations (account, transaction, contract)
- Author tracking with timestamps
Usage:
import { useAnnotations } from '../hooks/useAnnotations'
import { AnnotationsPanel } from '../components/collaboration/AnnotationsPanel'
function TransactionDetail({ transactionId }) {
const { addAnnotation, getAnnotationsForTarget } = useAnnotations()
const handleAddComment = async (content) => {
await addAnnotation('transaction', transactionId, content, 'user-123', 'You')
}
return <AnnotationsPanel type="transaction" targetId={transactionId} />
}See real-time cursor positions from other users viewing the same content.
Components:
CursorOverlay- Displays remote cursors with user labels- Integrated into
PresenceManager
Features:
- Real-time cursor position broadcasting
- Color-coded cursors per user
- User labels on cursors
- Smooth position transitions
Usage:
import { CursorOverlay } from '../components/collaboration/CursorOverlay'
import { usePresence } from '../hooks/usePresence'
function MyView({ accountId }) {
const { updateCursor } = usePresence()
const handleMouseMove = (e) => {
updateCursor(e.clientX, e.clientY, 'my-element')
}
return (
<div onMouseMove={handleMouseMove}>
<CursorOverlay accountId={accountId} />
</div>
)
}Existing feature enhanced with presence integration. Automatically syncs network, active tab, and connected address across browser tabs.
Components:
stateSync.js- Cross-tab synchronization via BroadcastChanneluseCollaboration- React hook for collaboration featuresCollaborativeView- UI for collaboration settings
Features:
- Automatic state propagation across tabs
- Shareable URL encoding (network + tab + address)
- Privacy warnings for public key sharing
- Tab count tracking
Usage:
import { useCollaboration } from '../hooks/useCollaboration'
import CollaborativeView from '../components/dashboard/CollaborativeView'
function Settings() {
const { syncStatus, connectedTabs, generateShareLink } = useCollaboration(store)
return <CollaborativeView store={store} />
}All real-time features use the browser's BroadcastChannel API:
// Presence channel
const PRESENCE_CHANNEL = 'stellar-dashboard-presence'
// Annotations channel
const ANNOTATIONS_CHANNEL = 'stellar-dashboard-annotations'
// State sync channel
const SYNC_CHANNEL = 'stellar-dashboard-sync'Annotations and user preferences are stored in IndexedDB:
// Annotations storage
const ANNOTATIONS_STORAGE_KEY = 'stellar-collaboration-annotations'
// User preferences
const PREFS_STORAGE_KEY = 'store:preferences'- No private keys shared: Only public keys and non-sensitive state are broadcast
- Allow-listed fields: Only specific state fields are synced (network, activeTab, connectedAddress, theme)
- Local-only: All communication stays within the browser; no external servers
- Privacy warnings: Users are warned when sharing URLs containing public keys
- Import the hook:
import { usePresence } from '../hooks/usePresence'- Initialize and update state:
const { updateAccount, updateActiveTab, updateSelection } = usePresence()
useEffect(() => {
updateAccount(accountId)
}, [accountId, updateAccount])
useEffect(() => {
updateActiveTab(tabName)
}, [tabName, updateActiveTab])- Add presence indicator:
<PresenceIndicator accountId={accountId} />- Import the hook:
import { useAnnotations } from '../hooks/useAnnotations'- Use annotation functions:
const { addAnnotation, getAnnotationsForTarget } = useAnnotations()
const handleAddComment = async (content) => {
await addAnnotation(type, targetId, content, authorId, authorName)
}- Render annotations panel:
<AnnotationsPanel type="account" targetId={accountId} />const {
users, // Array of active users
isInitialized, // Boolean, true when presence manager is ready
updateAccount, // (accountId: string | null) => void
updateActiveTab, // (tab: string) => void
updateCursor, // (x: number, y: number, element?: string) => void
updateSelection, // (type: string, id: string) => void
getUsersForAccount, // (accountId: string) => PresenceUser[]
getUserCount, // () => number
} = usePresence()const {
annotations, // Array of all annotations
isInitialized, // Boolean, true when store is ready
addAnnotation, // (type, targetId, content, authorId, authorName) => Promise<Annotation>
updateAnnotation, // (id, updates) => Promise<Annotation | null>
deleteAnnotation, // (id) => Promise<boolean>
resolveAnnotation, // (id) => Promise<boolean>
getAnnotationsForTarget, // (type, targetId) => Annotation[]
getAnnotationCount, // (type, targetId) => number
} = useAnnotations()interface PresenceUser {
id: string
tabId: string
accountId: string | null
activeTab: string
lastSeen: number
cursor?: {
x: number
y: number
element?: string
}
selection?: {
type: 'account' | 'transaction' | 'contract'
id: string
}
}interface Annotation {
id: string
type: 'account' | 'transaction' | 'contract'
targetId: string
content: string
authorId: string
authorName?: string
createdAt: number
updatedAt: number
resolved: boolean
}Collaboration components use the CSS in src/styles/collaboration.css. The styles are automatically imported via globals.css.
Styles use CSS custom properties that match the dashboard's design tokens:
--bg-base, --bg-surface, --bg-elevated
--border, --border-bright
--text-primary, --text-secondary, --text-muted
--cyan, --green, --amber, --red- BroadcastChannel: Supported in all modern browsers (Chrome 54+, Firefox 38+, Safari 15.4+)
- IndexedDB: Supported in all modern browsers
- Fallback: Graceful degradation when features are not supported
- Open the dashboard in multiple browser tabs
- Connect to the same account in each tab
- Observe the presence indicator showing viewer count
- Add annotations in one tab and see them appear in others
- Move cursor in one tab and observe cursor overlay in others
Collaboration features can be tested using Playwright's multi-tab capabilities:
test('presence sync across tabs', async ({ context }) => {
const page1 = await context.newPage()
const page2 = await context.newPage()
// Connect both pages to same account
await page1.goto('/?account=...')
await page2.goto('/?account=...')
// Verify presence indicator shows 2 viewers
const viewers1 = await page1.locator('.presence-indicator').textContent()
expect(viewers1).toContain('2 viewers')
})Potential improvements for collaboration features:
- WebSocket Integration: Add optional WebSocket server for cross-device collaboration
- User Authentication: Add user identity and authentication
- Rich Text Annotations: Support markdown, code blocks, and formatting
- Annotation Reactions: Add emoji reactions to annotations
- Voice/Video: Integrate WebRTC for voice/video collaboration
- Screen Sharing: Add screen sharing capabilities
- Version History: Track changes to annotations with version history
- Export/Import: Allow exporting annotations for backup
- Verify BroadcastChannel is supported in the browser
- Check that tabs are on the same origin
- Ensure presence manager is initialized
- Check browser console for errors
- Verify IndexedDB is enabled
- Check that annotations store is initialized
- Ensure BroadcastChannel is working
- Check for storage quota exceeded errors
- Ensure CursorOverlay component is rendered
- Check that cursor updates are being called
- Verify z-index is not being overridden
- Check that accountId matches between tabs
- Heartbeat interval: 5 seconds (configurable in PresenceManager)
- Presence timeout: 15 seconds (configurable in PresenceManager)
- Annotation history: Capped at 200 entries (configurable in AnnotationsStore)
- Cursor updates: Throttled to avoid excessive broadcasts
MIT - Part of the Stellar Dev Dashboard project.