Skip to content

Fix dashboard disconnect notification and flickering sidebar#65

Merged
JoshuaAFerguson merged 7 commits into
mainfrom
claude/fix-dashboard-disconnect-flicker-01MNbudhg3rBJPL4ZnauJKEG
Nov 17, 2025
Merged

Fix dashboard disconnect notification and flickering sidebar#65
JoshuaAFerguson merged 7 commits into
mainfrom
claude/fix-dashboard-disconnect-flicker-01MNbudhg3rBJPL4ZnauJKEG

Conversation

@JoshuaAFerguson

Copy link
Copy Markdown
Member

No description provided.

…ection

Issue: Dashboard sidebar was flickering endlessly when WebSocket
disconnected due to constant re-renders triggered by connection status
updates.

Root Cause:
- EnhancedWebSocketStatus countdown timer updated every second
- Each update caused Dashboard to re-render
- Dashboard re-renders forced Layout and sidebar to re-render
- Sidebar menu CSS transitions caused visible flickering

Changes:
1. Memoize Layout component with React.memo to prevent unnecessary
   re-renders when parent components update
2. Stabilize useNotificationQueue hook by wrapping addNotification
   in useCallback to return consistent function reference
3. Add documentation comments explaining the optimization

This ensures the sidebar only re-renders when the route actually
changes, not when Dashboard re-renders due to WebSocket state updates.
Issue: Frontend WebSocket connections to /api/v1/ws/sessions were
succeeding but receiving no data. The dashboard showed "Disconnected"
toasts and no session updates were being broadcast to clients.

Root Cause:
There were TWO separate WebSocket implementations running:
1. OLD wsManager (internal/websocket/) - Had broadcast logic that
   fetched sessions every 3s and metrics every 5s, but NO routes
   registered to use it
2. NEW websocketHandler (handlers/websocket.go) - Had registered
   routes at /api/v1/ws/sessions but NO broadcast logic

Frontend was connecting to NEW handler's routes but nothing was
broadcasting session updates through those connections.

Solution:
1. Remove the unused websocketHandler that wasn't broadcasting
2. Wire WebSocket routes directly to wsManager which has the
   working broadcast goroutines
3. Add WebSocket route handlers that:
   - Upgrade HTTP connections to WebSocket
   - Extract authenticated user ID from middleware
   - Delegate to wsManager.HandleSessionsWebSocket()
   - Automatically receive broadcasts every 3s (sessions) and 5s (metrics)

Changes:
- main.go: Replace websocketHandler.RegisterRoutes() with custom
  route handlers that use wsManager
- main.go: Add WebSocket upgrader configuration
- main.go: Wire /ws/sessions and /ws/cluster to wsManager methods
- main.go: Update imports to use internalWebsocket alias

Result: Frontend now receives real-time session updates every 3
seconds and metrics updates every 5 seconds via WebSocket.
Fix build errors:
- Change websocket.Upgrade() to upgrader.Upgrade()
- upgrader is the gorilla/websocket.Upgrader instance
- Fixes 'not enough arguments' compilation error
Add script to stop application containers while preserving database and data:
- Scales application deployments (controller, api, ui) to 0 replicas
- Does NOT touch PostgreSQL database or its PVC
- Does NOT delete any resources (deployments, services, PVCs)
- Preserves all data for quick restart after code updates

Workflow:
1. ./scripts/local-stop-apps.sh       # Stop apps
2. git pull                           # Update code
3. ./scripts/local-build.sh           # Rebuild images
4. ./scripts/local-deploy-kubectl.sh  # Deploy updates

This enables zero-downtime updates without losing database data.
Add automated port forwarding for StreamSpace services:

New Scripts:
- local-port-forward.sh: Start port forwards in background
  - UI: localhost:3000 -> streamspace-ui:80
  - API: localhost:8000 -> streamspace-api:8000
  - Waits for services to be ready before forwarding
  - Runs in background, logs to .port-forward-logs/
  - Tracks PIDs in .port-forward-pids/ for cleanup

- local-stop-port-forward.sh: Stop all port forwards
  - Gracefully kills background processes
  - Cleans up PID and log files

Integration:
- Updated local-deploy-kubectl.sh to auto-start port forwards
  - Set AUTO_PORT_FORWARD=false to disable
  - Default: enabled for convenience

Benefits:
- No manual port-forward commands needed
- Access UI/API immediately after deployment
- Port forwards persist across terminal sessions
- Easy cleanup with stop script

Usage:
  ./scripts/local-deploy-kubectl.sh  # Auto-starts port forwards
  ./scripts/local-port-forward.sh    # Manually start
  ./scripts/local-stop-port-forward.sh  # Stop all forwards
Add same auto port forwarding feature to local-deploy.sh (Helm version):
- Automatically starts port forwards after deployment
- Matches behavior of local-deploy-kubectl.sh
- Set AUTO_PORT_FORWARD=false to disable

Both deployment methods (Helm and kubectl) now have consistent behavior:
  ./scripts/local-deploy.sh         # Helm (auto port forwards)
  ./scripts/local-deploy-kubectl.sh # kubectl (auto port forwards)
Issue: Sidebar flickered continuously making navigation impossible.

Root Cause:
- useEnhancedWebSocket hook returned new object on every render
- EnhancedWebSocketStatus re-rendered on every countdown tick
- These re-renders propagated to Dashboard, then to Layout
- Layout re-renders caused sidebar to flicker with CSS transitions

Solution:
1. Memoize useEnhancedWebSocket return value with useMemo
   - Only updates when actual values change (isConnected, latency, etc.)
   - Prevents unnecessary object recreation
2. Wrap EnhancedWebSocketStatus with React.memo
   - Prevents re-renders when props unchanged
   - Countdown timer updates stay isolated to component
3. Combined with previous Layout memoization
   - Complete isolation of re-render chains

Result: Sidebar only re-renders on actual navigation, not on
WebSocket status updates or countdown ticks.
@JoshuaAFerguson JoshuaAFerguson merged commit 11d9ca1 into main Nov 17, 2025
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

https://github.com/JoshuaAFerguson/streamspace/blob/11d9ca1845115f7f36fed2c341ac1db867f74803/api/cmd/main.go#L371-L375
P1 Badge Restore origin checks on WebSocket upgrader

The new WebSocket upgrader now hardcodes CheckOrigin to always return true, which means /api/v1/ws/* will accept WebSocket handshakes from any origin. Previously the handler used the shared upgrader that enforced the ALLOWED_ORIGINS list to block cross-site connections. In environments where auth is cookie-based or tokens are sent automatically, this change reintroduces CSRF/data-leak risk by allowing arbitrary websites to open authenticated WebSocket sessions. The upgrader should reuse the existing origin validation instead of allowing all origins unconditionally.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@JoshuaAFerguson JoshuaAFerguson deleted the claude/fix-dashboard-disconnect-flicker-01MNbudhg3rBJPL4ZnauJKEG branch November 18, 2025 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants