* fix(flux): resolve workspace identifier mismatch between frontend, server, and consumer
The flux service had a fundamental identifier mismatch:
- Frontend connects to /events/{workspaceSlug} (slug)
- Consumer receives events with workspace_id (UUID from outbox)
- Auth middleware needs slug for Django API calls
Solution: Redis-backed WorkspaceRegistry for UUID↔slug mapping
Changes:
- Add WorkspaceRegistry service backed by Redis with bidirectional
UUID↔slug mapping (24h TTL per key)
- Add getWorkspace method to AuthService (calls GET /api/workspaces/{slug}/)
- Update auth middleware to fetch workspace UUID on connection and
register the mapping; store both workspaceSlug and workspaceId on socket
- Update consumer to resolve workspace_id→slug via registry before emitting
- Remove workspace_slug field from EventMessage schema (uses registry instead)
- Fix lint warnings: use Effect.async/Effect.promise idiomatically,
wrap async middleware to avoid no-misused-promises
- Frontend already updated to use workspaceSlug throughout (prior work)
* refactor(web): improve socket client with useSyncExternalStore
- Use useSyncExternalStore for tear-safe status reads from SocketClient
- Replace single statusChangeHandler with Set for multi-listener support
- Simplify work-item-detail revalidation with inverse logic instead of
fragile 15-item event type list
- Rename work-item.tsx → work-item.ts (pure type file, no JSX)
* fix(flux): update .env.example AMQP_URL with credentials and vhost
The consumer needs the RabbitMQ user, password, and vhost to connect
to the correct virtual host where plane.event_stream exchange exists.
* fix(flux): handle registry errors in consumer, remove redundant httpServer.close
- Wrap registry.getSlugById() with Effect.either in consumer to catch
Redis errors, log them, and ack the message instead of crashing the
stream and leaving messages stuck/unacked.
- Remove redundant httpServer.close() in server shutdown — io.close()
already closes the underlying httpServer when one was passed to the
constructor.
* fix: update lockfile after removing axios from flux
* fix(flux): sliding TTL on registry reads, isolate status listeners
- Add sliding TTL (expire refresh) on successful WorkspaceRegistry reads
to prevent mappings expiring while actively used by the consumer.
- Wrap status change listener calls in try/catch to prevent one failing
listener from blocking others.
- Remove void prefix on socket.join() calls (synchronous at runtime).
* Remove dead code from events handler and client types
- Remove unused workspace:/user: room joins (nothing emits to them)
- Remove unused connected event emit and client type
- Remove onAny logger (production noise)
- Add ALLOWED_ENTITY_TYPES validation on subscribe/unsubscribe
- Use namespace.sockets.size for connection stats
* fix(flux): refactor socket system to minimal signal architecture
Flux server:
- AMQP service with acquireRelease lifecycle and reconnect loop
- Consumer parses outbox events, builds minimal signals, emits via Redis
- SocketEmitter uses @socket.io/redis-emitter for cross-process delivery
- Room-based routing: only subscribed clients receive entity events
- WorkspaceRegistry with bidirectional slug/UUID mapping and sliding TTL
- RedisClient as shared Context.Tag singleton
Web client:
- Replace verbose type system with minimal TEntityEvent
- Replace useSocketEvent/useFilteredSocketEvent with useEntityEvent
- Room-based subscriptions via subscribe:entity/unsubscribe:entity
- Remove deleted work-item.ts types file
- Simplify provider context to status + subscribeEntity
* docs(flux): document TODO for excluding originating connection from broadcast
* fix(flux): update stale JSDoc and use register() for resilient registry refresh
- Fix JSDoc on setupEventsNamespace to reflect current room-based architecture
- Use register() instead of refreshTtl() for periodic refresh so mappings
survive Redis eviction/restart instead of silently no-oping on EXPIRE