- Backend: room creation for live streams, permissions CanJoin/CanSend/CanRead for stream rooms - LiveViewChat: useLiveStreamChat hook, WebSocket connection, stream_id as room - LiveViewPlayer: real-time viewer count via polling (5s) - Media Session: seekbackward/seekforward handlers (10s step) - GoLiveView.stories.tsx: Default, Loading, Error, StreamKeyVisible - Docs: API_REFERENCE, CHANGELOG, PROJECT_STATE, FEATURE_STATUS, RETROSPECTIVE_V0703 - SCOPE_CONTROL, .cursorrules: update to v0.801 - Archive V0_703_RELEASE_SCOPE.md
431 lines
18 KiB
Markdown
431 lines
18 KiB
Markdown
# V0.703 Release Scope — Go Live & Streaming Complet
|
||
|
||
**Statut** : En cours
|
||
**Phase** : 7 (Production Readiness — Finale)
|
||
**Prérequis** : v0.702 (taguée)
|
||
**Date cible** : TBD
|
||
**Estimation** : ~3 sprints (15 jours ouvrés)
|
||
**Précédente** : [v0.702](archive/V0_702_RELEASE_SCOPE.md)
|
||
|
||
---
|
||
|
||
## 1. Objectif
|
||
|
||
Dernière version de la Phase 7. Complète le module Live Streaming en ajoutant la fonctionnalité **Go Live** (démarrage de stream), actuellement bloquée par un toast "coming soon" dans la Navbar. Ajoute la **gestion de stream keys**, les endpoints **start/stop/schedule**, le **chat live connecté WebSocket**, et des **améliorations player** (vitesse de lecture, Media Session API).
|
||
|
||
---
|
||
|
||
## 2. État actuel (post-v0.702)
|
||
|
||
| Composant | État | Détail |
|
||
|-----------|------|--------|
|
||
| **LiveStream model** | ✅ Livré | `live_streams` table, GORM model avec StreamKey (json:"-") |
|
||
| **GET /live/streams** | ✅ Livré | Liste publique, filtre `?is_live=true` |
|
||
| **GET /live/streams/:id** | ✅ Livré | Détail d'un stream |
|
||
| **POST /live/streams** | ✅ Livré | Création (auth required), mais pas de stream key generation |
|
||
| **Stream events handler** | ✅ Livré | `POST /internal/stream-events` (stream_started, stream_ended, listener_joined, listener_left) |
|
||
| **LiveView (frontend)** | ✅ Livré | Visualisation de streams (player, info, recommended, chat mock) |
|
||
| **Go Live (frontend)** | ❌ Absent | Navbar "Go Live" → toast "coming soon" |
|
||
| **Stream key generation** | ❌ Absent | StreamKey field exists mais jamais généré ni exposé |
|
||
| **GET /live/streams/my/key** | ❌ Absent | Endpoint pour récupérer sa stream key |
|
||
| **PUT /live/streams/:id** | ❌ Absent | Mise à jour métadonnées pendant le live |
|
||
| **Stream scheduling** | ❌ Absent | Pas de scheduled_at pour planifier un stream |
|
||
| **Live chat WebSocket** | ❌ Absent | LiveViewChat utilise des données mock statiques |
|
||
| **VOD (replays)** | ❌ Absent | Streams terminés non consultables |
|
||
| **Playback speed** | ❌ Absent | Pas de contrôle 0.5x–2x |
|
||
| **Media Session API** | ❌ Absent | Pas de contrôles OS (notification area) |
|
||
|
||
---
|
||
|
||
## 3. Lots
|
||
|
||
### Lot GL1 — Go Live Backend
|
||
|
||
**Objectif** : Compléter l'API live streaming avec stream key, mise à jour, scheduling et VOD.
|
||
|
||
| # | Tâche | Fichiers impactés | Effort |
|
||
|---|-------|--------------------|--------|
|
||
| GL1-01 | Migration `117_live_streams_go_live.sql` — ajout colonnes `scheduled_at`, `stream_url`, `is_vod` ; update `stream_key` NOT NULL avec default UUID | `migrations/117_live_streams_go_live.sql` | S |
|
||
| GL1-02 | Mise à jour modèle `LiveStream` — ajout champs `ScheduledAt`, `StreamURL`, `IsVOD` | `internal/models/live_stream.go` | S |
|
||
| GL1-03 | `GenerateStreamKey` dans service — génère un UUID v4 unique pour stream_key lors du Create | `internal/services/live_stream_service.go` | S |
|
||
| GL1-04 | `GET /live/streams/me` — liste les streams de l'utilisateur authentifié (incluant stream_key) | `internal/handlers/live_stream_handler.go`, routes | M |
|
||
| GL1-05 | `GET /live/streams/me/key` — retourne la stream key de l'utilisateur (crée un stream draft si nécessaire) | `internal/handlers/live_stream_handler.go`, routes | S |
|
||
| GL1-06 | `POST /live/streams/me/key/regenerate` — régénère la stream key | `internal/handlers/live_stream_handler.go`, routes | S |
|
||
| GL1-07 | `PUT /live/streams/:id` — mise à jour titre/description/category/tags pendant ou avant le live | `internal/handlers/live_stream_handler.go`, routes | S |
|
||
| GL1-08 | `GET /live/streams?is_vod=true` — listing des streams terminés (VOD replays) | `internal/services/live_stream_service.go`, repository | S |
|
||
| GL1-09 | Prometheus metrics — `veza_live_streams_active`, `veza_live_stream_viewers_total` | `internal/monitoring/metrics.go` | S |
|
||
| GL1-10 | Tests unitaires — stream key generation, CRUD complet, ownership check, VOD filter | `internal/services/live_stream_service_test.go` (nouveau) | M |
|
||
|
||
### Lot GL2 — Go Live Frontend
|
||
|
||
**Objectif** : Page Go Live pour configurer et démarrer un stream, afficher la stream key, instructions OBS.
|
||
|
||
| # | Tâche | Fichiers impactés | Effort |
|
||
|---|-------|--------------------|--------|
|
||
| GL2-01 | `GoLiveView` — formulaire (titre, description, catégorie, tags, thumbnail), affichage stream key avec bouton copy, instructions OBS/Streamlabs | `apps/web/src/features/live/pages/go-live-page/GoLiveView.tsx` (nouveau) | L |
|
||
| GL2-02 | `GoLivePage` — wrapper avec fetch stream key, state management | `apps/web/src/features/live/pages/GoLivePage.tsx` (nouveau) | M |
|
||
| GL2-03 | Lazy export `LazyGoLive` — createLazyComponent pour GoLivePage | `apps/web/src/components/ui/lazy-component/lazyExports.ts` | S |
|
||
| GL2-04 | Route `/live/go-live` dans routeConfig — protégée auth | `apps/web/src/router/routeConfig.tsx` | S |
|
||
| GL2-05 | Navbar "Go Live" → navigate `/live/go-live` au lieu du toast | `apps/web/src/components/layout/Navbar.tsx` | S |
|
||
| GL2-06 | Service frontend `liveService` — `getMyStreams`, `getMyStreamKey`, `regenerateStreamKey`, `updateStream`, `createStream` | `apps/web/src/services/liveService.ts` (modifier) | M |
|
||
| GL2-07 | MSW handlers — mock GET /live/streams/me, GET /live/streams/me/key, POST regenerate, PUT :id | `apps/web/src/mocks/handlers-live.ts` (nouveau) | M |
|
||
| GL2-08 | Story `GoLiveView.stories.tsx` — états Default, Loading, Error, StreamKeyVisible | `apps/web/src/features/live/pages/go-live-page/GoLiveView.stories.tsx` (nouveau) | M |
|
||
|
||
### Lot GL3 — Live Chat & Interaction
|
||
|
||
**Objectif** : Connecter le chat live aux WebSocket existants pour un chat temps réel par stream.
|
||
|
||
| # | Tâche | Fichiers impactés | Effort |
|
||
|---|-------|--------------------|--------|
|
||
| GL3-01 | Chat room par stream — utiliser le système de conversation WebSocket existant (JoinConversation avec stream_id comme room) | `internal/websocket/chat/handlers.go` | M |
|
||
| GL3-02 | `LiveViewChat` connecté WebSocket — remplacer les données mock par le hook useChat existant | `apps/web/src/features/live/pages/live-page/LiveViewChat.tsx` | M |
|
||
| GL3-03 | Viewer count temps réel — écouter les events `listener_joined`/`listener_left` via WebSocket | `apps/web/src/features/live/pages/live-page/LiveViewPlayer.tsx` | S |
|
||
| GL3-04 | Tests — chat join/leave stream room, message broadcast | Tests | M |
|
||
|
||
### Lot GL4 — Player Enhancements
|
||
|
||
**Objectif** : Vitesse de lecture et intégration Media Session API pour contrôles OS.
|
||
|
||
| # | Tâche | Fichiers impactés | Effort |
|
||
|---|-------|--------------------|--------|
|
||
| GL4-01 | Playback speed control — bouton 0.5x, 0.75x, 1x, 1.25x, 1.5x, 2x dans le player | `apps/web/src/features/player/components/audio-player/` | M |
|
||
| GL4-02 | Media Session API — navigator.mediaSession avec metadata (title, artist, artwork), action handlers (play, pause, seekbackward, seekforward, previoustrack, nexttrack) | `apps/web/src/features/player/hooks/` | M |
|
||
| GL4-03 | Tests — playback speed persistence, media session metadata update | Tests | S |
|
||
|
||
### Lot QA1 — Tests & Release
|
||
|
||
| # | Tâche | Fichiers impactés | Effort |
|
||
|---|-------|--------------------|--------|
|
||
| QA1-01 | Smoke test v0.703 — checklist complète | `docs/SMOKE_TEST_V0703.md` | S |
|
||
| QA1-02 | Mise à jour PROJECT_STATE, FEATURE_STATUS, CHANGELOG | `docs/` | S |
|
||
| QA1-03 | Rétrospective v0.703, archivage scope, placeholder v0.801, tag | `docs/`, Git | S |
|
||
|
||
---
|
||
|
||
## 4. Hors scope v0.703
|
||
|
||
| Élément | Version cible |
|
||
|---------|---------------|
|
||
| 2FA SMS / Passkeys | v0.104 |
|
||
| Themes (high contrast, compact) | v0.801 |
|
||
| Accessibilité WCAG AA | v0.801 |
|
||
| Cloud Storage avancé (versioning, backup) | v0.802 |
|
||
| OpenAPI/Swagger auto-gen | v0.803 |
|
||
| Wishlist marketplace | v0.901 |
|
||
| Promotions dynamiques (flash sales) | v0.901 |
|
||
| Dispute / réclamation client | v0.901 |
|
||
| Multi-camera support | v2.0 |
|
||
| Donations/tips live | v2.0 |
|
||
| Clipping moments | v2.0 |
|
||
|
||
---
|
||
|
||
## 5. Détail technique — Lot GL1
|
||
|
||
### 5.1 Migration 117_live_streams_go_live.sql
|
||
|
||
```sql
|
||
ALTER TABLE live_streams
|
||
ADD COLUMN IF NOT EXISTS scheduled_at TIMESTAMPTZ,
|
||
ADD COLUMN IF NOT EXISTS stream_url TEXT DEFAULT '',
|
||
ADD COLUMN IF NOT EXISTS is_vod BOOLEAN NOT NULL DEFAULT false;
|
||
|
||
UPDATE live_streams SET stream_key = gen_random_uuid()::text WHERE stream_key = '' OR stream_key IS NULL;
|
||
|
||
ALTER TABLE live_streams ALTER COLUMN stream_key SET NOT NULL;
|
||
ALTER TABLE live_streams ALTER COLUMN stream_key SET DEFAULT gen_random_uuid()::text;
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_live_streams_user_id ON live_streams(user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_live_streams_is_live ON live_streams(is_live) WHERE is_live = true;
|
||
CREATE INDEX IF NOT EXISTS idx_live_streams_scheduled ON live_streams(scheduled_at) WHERE scheduled_at IS NOT NULL;
|
||
```
|
||
|
||
### 5.2 Modèle LiveStream mis à jour
|
||
|
||
```go
|
||
type LiveStream struct {
|
||
// ... champs existants ...
|
||
ScheduledAt *time.Time `json:"scheduled_at,omitempty" db:"scheduled_at"`
|
||
StreamURL string `gorm:"type:text;default:''" json:"stream_url,omitempty" db:"stream_url"`
|
||
IsVOD bool `gorm:"default:false" json:"is_vod" db:"is_vod"`
|
||
}
|
||
```
|
||
|
||
### 5.3 Stream Key Generation
|
||
|
||
```go
|
||
func (s *LiveStreamService) Create(ctx context.Context, userID uuid.UUID, stream *models.LiveStream) (*models.LiveStream, error) {
|
||
if stream.Title == "" {
|
||
return nil, errors.New("title is required")
|
||
}
|
||
stream.UserID = userID
|
||
stream.StreamKey = uuid.New().String()
|
||
if stream.StreamerName == "" {
|
||
stream.StreamerName = "Streamer"
|
||
}
|
||
if err := s.repo.Create(ctx, stream); err != nil {
|
||
return nil, err
|
||
}
|
||
return stream, nil
|
||
}
|
||
```
|
||
|
||
### 5.4 GET /live/streams/me/key
|
||
|
||
```go
|
||
func (h *LiveStreamHandler) GetMyStreamKey(c *gin.Context) {
|
||
userID, ok := GetUserIDUUID(c)
|
||
if !ok { return }
|
||
|
||
streams, err := h.service.ListByUser(c.Request.Context(), userID)
|
||
if err != nil {
|
||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get streams", err))
|
||
return
|
||
}
|
||
|
||
var streamKey string
|
||
if len(streams) > 0 {
|
||
streamKey = streams[0].StreamKey
|
||
} else {
|
||
draft := &models.LiveStream{Title: "My Stream"}
|
||
created, err := h.service.Create(c.Request.Context(), userID, draft)
|
||
if err != nil {
|
||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create stream", err))
|
||
return
|
||
}
|
||
streamKey = created.StreamKey
|
||
}
|
||
|
||
RespondSuccess(c, http.StatusOK, gin.H{
|
||
"stream_key": streamKey,
|
||
"rtmp_url": "rtmp://stream.veza.app/live",
|
||
})
|
||
}
|
||
```
|
||
|
||
### 5.5 VOD — Streams terminés
|
||
|
||
Quand `SetIsLive(false)` est appelé (stream_ended event), le stream reste en DB avec `ended_at` set. On ajoute un filtre `is_vod` qui retourne les streams avec `ended_at IS NOT NULL AND is_live = false`.
|
||
|
||
### 5.6 Scheduled Streams
|
||
|
||
Le champ `scheduled_at` permet de planifier un stream futur. L'endpoint `POST /live/streams` accepte `scheduled_at` dans le body. Le frontend affiche les streams planifiés avec un badge "Scheduled" et un countdown.
|
||
|
||
---
|
||
|
||
## 6. Détail technique — Lot GL2
|
||
|
||
### 6.1 GoLiveView
|
||
|
||
```typescript
|
||
interface GoLiveViewProps {
|
||
streamKey: string | null;
|
||
rtmpUrl: string;
|
||
onCreateStream: (data: CreateStreamData) => Promise<void>;
|
||
onRegenerateKey: () => Promise<void>;
|
||
isLoading: boolean;
|
||
}
|
||
|
||
interface CreateStreamData {
|
||
title: string;
|
||
description: string;
|
||
category: string;
|
||
tags: string[];
|
||
thumbnailUrl?: string;
|
||
scheduledAt?: string;
|
||
}
|
||
```
|
||
|
||
Sections :
|
||
1. **Stream Configuration** — formulaire titre/description/category/tags
|
||
2. **Stream Key** — affichage masqué par défaut, bouton reveal, bouton copy, bouton regenerate
|
||
3. **Connection Info** — RTMP URL + stream key, instructions OBS/Streamlabs
|
||
4. **Preview** — thumbnail upload, scheduled_at picker
|
||
5. **Actions** — bouton "Go Live Now" ou "Schedule Stream"
|
||
|
||
### 6.2 Navbar update
|
||
|
||
```typescript
|
||
// Avant (toast)
|
||
onClick={() => toast.info('Live streaming feature coming soon')}
|
||
|
||
// Après (navigation)
|
||
onClick={() => { onNavigate('go-live'); setShowUserMenu(false); }}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Détail technique — Lot GL4
|
||
|
||
### 7.1 Playback Speed
|
||
|
||
```typescript
|
||
const SPEED_OPTIONS = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
||
|
||
function PlaybackSpeedButton({ currentSpeed, onSpeedChange }: Props) {
|
||
// Cycle through speeds on click, or dropdown on hover
|
||
}
|
||
```
|
||
|
||
### 7.2 Media Session API
|
||
|
||
```typescript
|
||
function useMediaSession(track: Track | null, actions: MediaSessionActions) {
|
||
useEffect(() => {
|
||
if (!('mediaSession' in navigator) || !track) return;
|
||
|
||
navigator.mediaSession.metadata = new MediaMetadata({
|
||
title: track.title,
|
||
artist: track.artist,
|
||
artwork: track.coverUrl ? [{ src: track.coverUrl, sizes: '512x512' }] : [],
|
||
});
|
||
|
||
navigator.mediaSession.setActionHandler('play', actions.onPlay);
|
||
navigator.mediaSession.setActionHandler('pause', actions.onPause);
|
||
navigator.mediaSession.setActionHandler('previoustrack', actions.onPrevious);
|
||
navigator.mediaSession.setActionHandler('nexttrack', actions.onNext);
|
||
navigator.mediaSession.setActionHandler('seekbackward', actions.onSeekBackward);
|
||
navigator.mediaSession.setActionHandler('seekforward', actions.onSeekForward);
|
||
}, [track, actions]);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Fichiers impactés (récapitulatif)
|
||
|
||
### Backend Go (nouveau)
|
||
|
||
| Fichier | Action |
|
||
|---------|--------|
|
||
| `migrations/117_live_streams_go_live.sql` | Nouveau — colonnes scheduled_at, stream_url, is_vod |
|
||
| `internal/services/live_stream_service_test.go` | Nouveau — tests unitaires live stream |
|
||
|
||
### Backend Go (modifier)
|
||
|
||
| Fichier | Action |
|
||
|---------|--------|
|
||
| `internal/models/live_stream.go` | Ajout ScheduledAt, StreamURL, IsVOD |
|
||
| `internal/services/live_stream_service.go` | Stream key generation, ListByUser, RegenerateKey |
|
||
| `internal/handlers/live_stream_handler.go` | GET me, GET me/key, POST regenerate, PUT :id |
|
||
| `internal/api/routes_live.go` | Ajout routes protégées (me, key, regenerate, PUT) |
|
||
| `internal/repositories/live_stream_repository.go` | ListByUserID, filtre VOD |
|
||
| `internal/monitoring/metrics.go` | Métriques live streams |
|
||
|
||
### Frontend (nouveau)
|
||
|
||
| Fichier | Action |
|
||
|---------|--------|
|
||
| `apps/web/src/features/live/pages/GoLivePage.tsx` | Nouveau — page wrapper |
|
||
| `apps/web/src/features/live/pages/go-live-page/GoLiveView.tsx` | Nouveau — formulaire Go Live |
|
||
| `apps/web/src/features/live/pages/go-live-page/GoLiveView.stories.tsx` | Nouveau — stories |
|
||
| `apps/web/src/mocks/handlers-live.ts` | Nouveau — MSW handlers live |
|
||
|
||
### Frontend (modifier)
|
||
|
||
| Fichier | Action |
|
||
|---------|--------|
|
||
| `apps/web/src/components/layout/Navbar.tsx` | Remplacer toast par navigation |
|
||
| `apps/web/src/services/liveService.ts` | Ajout getMyStreams, getMyStreamKey, regenerateStreamKey, updateStream |
|
||
| `apps/web/src/router/routeConfig.tsx` | Route /live/go-live |
|
||
| `apps/web/src/components/ui/lazy-component/lazyExports.ts` | LazyGoLive |
|
||
| `apps/web/src/features/live/pages/live-page/LiveViewChat.tsx` | Connecter WebSocket |
|
||
| `apps/web/src/features/live/pages/live-page/LiveViewPlayer.tsx` | Viewer count real-time |
|
||
| `apps/web/src/features/player/components/audio-player/` | Playback speed control |
|
||
| `apps/web/src/features/player/hooks/` | Media Session API hook |
|
||
|
||
### Documentation
|
||
|
||
| Fichier | Action |
|
||
|---------|--------|
|
||
| `docs/API_REFERENCE.md` | Ajout section Live Streaming |
|
||
| `docs/SMOKE_TEST_V0703.md` | Nouveau — checklist |
|
||
| `docs/PROJECT_STATE.md` | Section v0.703 |
|
||
| `docs/FEATURE_STATUS.md` | Mise à jour |
|
||
| `CHANGELOG.md` | Section v0.703 |
|
||
| `docs/RETROSPECTIVE_V0703.md` | Nouveau (fin de release) |
|
||
|
||
---
|
||
|
||
## 9. Critères d'acceptation globaux
|
||
|
||
- [ ] Stream key générée automatiquement lors du `POST /live/streams`
|
||
- [ ] `GET /live/streams/me/key` retourne stream key + RTMP URL (auth required)
|
||
- [ ] `POST /live/streams/me/key/regenerate` génère une nouvelle stream key
|
||
- [ ] `PUT /live/streams/:id` met à jour les métadonnées (ownership check)
|
||
- [ ] `GET /live/streams?is_vod=true` retourne les streams terminés
|
||
- [ ] Frontend GoLivePage : formulaire, stream key visible/copiable, instructions OBS
|
||
- [ ] Navbar "Go Live" navigue vers `/live/go-live` au lieu du toast
|
||
- [ ] LiveViewChat connecté WebSocket (messages en temps réel)
|
||
- [ ] Viewer count mis à jour en temps réel via events
|
||
- [ ] Playback speed : 6 vitesses (0.5x–2x), persistance
|
||
- [ ] Media Session API : metadata (title, artist, artwork), action handlers
|
||
- [ ] Stories GoLiveView : Default, Loading, Error, StreamKeyVisible
|
||
- [ ] MSW handlers live : GET me, GET me/key, POST regenerate, PUT :id
|
||
- [ ] Tests unitaires backend : stream key, CRUD, ownership, VOD
|
||
- [ ] Tag v0.703 créé
|
||
|
||
---
|
||
|
||
## 10. Risques
|
||
|
||
| Risque | Mitigation |
|
||
|--------|------------|
|
||
| Stream key exposée en clair dans le frontend | Masquée par défaut, reveal on click, HTTPS obligatoire |
|
||
| RTMP server non configuré (stream server absent) | Les endpoints backend sont prêts, stream server Rust à configurer séparément |
|
||
| LiveViewChat charge excessive avec beaucoup de viewers | Rate limiting WebSocket existant (v0.503), pagination historique |
|
||
| Media Session API non supportée par tous les navigateurs | Feature detection avec `if ('mediaSession' in navigator)` |
|
||
| VOD storage (streams enregistrés) | v0.703 = métadonnées uniquement, pas de stockage vidéo réel (future: S3 segments) |
|
||
|
||
---
|
||
|
||
## 11. Architecture
|
||
|
||
```mermaid
|
||
graph TD
|
||
subgraph backend [Backend Go]
|
||
API[API Routes /live/*]
|
||
SVC[LiveStreamService]
|
||
REPO[LiveStreamRepository]
|
||
DB[(PostgreSQL)]
|
||
WS[WebSocket Hub]
|
||
end
|
||
|
||
subgraph frontend [Frontend React]
|
||
GL[GoLivePage]
|
||
LV[LiveView]
|
||
LC[LiveViewChat]
|
||
PL[Player]
|
||
end
|
||
|
||
subgraph external [External]
|
||
OBS[OBS/Streamlabs]
|
||
SS[Stream Server Rust]
|
||
end
|
||
|
||
GL -->|"POST /live/streams"| API
|
||
GL -->|"GET /live/streams/me/key"| API
|
||
LV -->|"GET /live/streams"| API
|
||
LC -->|"WebSocket /ws"| WS
|
||
PL -->|"Media Session API"| PL
|
||
|
||
OBS -->|"RTMP stream_key"| SS
|
||
SS -->|"POST /internal/stream-events"| API
|
||
API --> SVC
|
||
SVC --> REPO
|
||
REPO --> DB
|
||
WS -->|"chat messages"| LC
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Références
|
||
|
||
- [RETROSPECTIVE_V0702.md](RETROSPECTIVE_V0702.md)
|
||
- [V0_702_RELEASE_SCOPE.md](archive/V0_702_RELEASE_SCOPE.md)
|
||
- [SCOPE_CONTROL.md](SCOPE_CONTROL.md)
|
||
- `veza-backend-api/internal/handlers/live_stream_handler.go` (handlers existants)
|
||
- `veza-backend-api/internal/services/live_stream_service.go` (service existant)
|
||
- `veza-backend-api/internal/models/live_stream.go` (modèle existant)
|
||
- `apps/web/src/features/live/pages/live-page/LiveView.tsx` (composant vue)
|
||
- `apps/web/src/components/layout/Navbar.tsx` L223 (toast "coming soon")
|