diff --git a/.dockerignore b/.dockerignore index 701dcef..484f993 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,7 +10,6 @@ .env.* !.env.example .git -.claude .vscode .idea *.log diff --git a/.env.example b/.env.example index 032d864..c89554e 100644 --- a/.env.example +++ b/.env.example @@ -30,7 +30,11 @@ ALLOWED_ORIGIN= ADMIN_TOKEN= # Rooms one IP may create per minute ROOM_CREATE_LIMIT_PER_MINUTE=10 -# Set to true behind a reverse proxy (Caddy / Nginx) so rate limits see the real client IP +# Simultaneous WebSocket connections from one IP (raise it when many streamers share one IP) +MAX_SOCKETS_PER_IP=64 +# Set to true behind a reverse proxy (Caddy / Nginx) so rate limits see the real client IP. +# Left false behind a proxy, every client shares its IP and the per-IP limits (room creation, +# unknown room codes, 64 WebSocket connections) apply to all of them together. TRUST_PROXY=false # Note: the OBS WebSocket address / password are entered in each streamer's Dock diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eb02e87..302fa20 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,10 @@ updates: directory: / schedule: interval: weekly + ignore: + # @types/node follows the Node.js runtime (engines.node and the Docker image), not the latest major + - dependency-name: '@types/node' + update-types: ['version-update:semver-major'] groups: # @vitest/coverage-v8 pins vitest exactly (peer dependency), so they must move together vitest: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82519d0..00e3f14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,8 @@ jobs: - run: npm ci - run: npm run build - run: npx playwright install --with-deps chromium + # Unit + component tests in Chromium, with the coverage thresholds of vitest.config.mts + - run: npm run test:coverage - run: npm run e2e - uses: actions/upload-artifact@v7 @@ -59,6 +61,7 @@ jobs: path: | playwright-report/ test-results/ + coverage/ retention-days: 14 docker: diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..96d9bf6 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,33 @@ +name: Mutation testing + +# On demand only (Actions -> Mutation testing -> Run workflow): it takes minutes and its score is a +# review aid, not a gate. Stryker is installed for the run, not added to the project's dependencies. +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + stryker: + name: Stryker (timecode and exporters) + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + + - run: npm ci + - run: npm install --no-save @stryker-mutator/core@10.1.1 @stryker-mutator/vitest-runner@10.1.1 + - run: npx stryker run + + - uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: mutation-report + path: reports/mutation/ + retention-days: 14 diff --git a/.gitignore b/.gitignore index a182104..65d3b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,14 +6,15 @@ node_modules/ # Production & Build outputs dist/ -# Playwright E2E artifacts +# Playwright E2E and Vitest browser-mode artifacts e2e/.data/ +.vitest/ +.preview-data/ test-results/ playwright-report/ blob-report/ -# Local Claude Code launch configs -.claude/ +# Local launch configs build/ out/ .cache/ @@ -51,6 +52,9 @@ pnpm-debug.log* # Testing & Coverage coverage/ +# Mutation testing (stryker.config.mjs) +reports/mutation/ +.stryker-tmp/ # OS generated files .DS_Store diff --git a/.prettierignore b/.prettierignore index 886f461..d36d7a9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,5 +3,4 @@ coverage node_modules package-lock.json *.sqlite* -.claude CHANGELOG.md diff --git a/ACCESSIBILITY.es.md b/ACCESSIBILITY.es.md new file mode 100644 index 0000000..0f923e2 --- /dev/null +++ b/ACCESSIBILITY.es.md @@ -0,0 +1,53 @@ +# Declaración de accesibilidad de StreamCollab + +[English](ACCESSIBILITY.md) | **Español** + +StreamCollab busca cumplir las **Pautas de Accesibilidad para el Contenido Web (WCAG) 2.1, nivel AA** en la Dock, el Panel del director y el HUD. Es un objetivo, no una declaración de conformidad: el proyecto no ha pasado una auditoría de accesibilidad independiente. Esta página explica qué se comprueba automáticamente, qué no se ha probado todavía y cómo informar de una barrera. + +--- + +## Qué se comprueba en cada cambio + +- **axe-core (reglas WCAG 2.1 A y AA)** se ejecuta en la suite end-to-end ([`e2e/a11y.spec.ts`](e2e/a11y.spec.ts)), en inglés y en español, sobre: + - la Dock, vacía y con un marcador y los ajustes de OBS abiertos, + - el Panel del director, su diálogo de inicio de sesión y su diálogo de exportación, + - el Panel del director con un miembro, marcadores y un marcador en edición, + - la vista previa del HUD y el HUD en vivo mostrando el aviso de un marcador. + + Cualquier infracción hace fallar el build. Las herramientas automáticas solo detectan una parte de los problemas WCAG (contraste, nombres, roles, estructura); no pueden juzgar, por ejemplo, si una etiqueta tiene sentido. +- **Diálogos** (Exportar, Iniciar sesión): se prueban en Chromium ([`packages/client/test/components.browser.test.tsx`](packages/client/test/components.browser.test.tsx)). Al abrirse, el foco entra en el diálogo; `Tab` / `Mayús+Tab` no salen de él; `Escape` lo cierra y el foco vuelve al botón que lo abrió. +- **Docks estrechas:** la suite end-to-end comprueba que todos los controles de la Dock siguen en pantalla con 300, 360 y 420 px de ancho. +- **Reflow (WCAG 1.4.10):** la suite end-to-end comprueba que el Panel del director, con una sesión en marcha, no necesita desplazamiento horizontal con 320 px de ancho. +- **Foco tras iniciar / detener la sesión:** cuando se sustituye el botón Iniciar o Detener que tenía el foco, lo recibe el nuevo en vez de la página, también si la sesión se inició o detuvo desde otra pestaña (tests end-to-end). + +## Qué hace StreamCollab + +- **Teclado:** todos los controles son botones, enlaces, campos o desplegables nativos. En la Dock, `1`–`5` añaden un marcador y `0` o `Ctrl+Z` / `Cmd+Z` deshacen el último tuyo. Los atajos se ignoran mientras escribes en un campo, con un diálogo abierto, al mantener la tecla pulsada y con `Ctrl` / `Alt` / `Cmd` (salvo el atajo de deshacer). +- **Diálogos** con `role="dialog"`, `aria-modal="true"` y una etiqueta tomada de su título. +- **Mensajes de estado:** "Servidor desconectado" en la Dock es un elemento `role="status"`. "Marcador no enviado" y la pregunta de confianza en otro servidor son `role="alert"`. +- **No solo color:** las categorías de marcador siempre se muestran con su nombre (y un icono en los botones de la Dock), nunca solo con un color. +- **Movimiento reducido:** con la preferencia del sistema `prefers-reduced-motion: reduce` se suprimen animaciones y transiciones. +- **Sonidos opcionales del HUD:** una fuente HUD abierta con `?sound=1` reproduce un tono corto y distinto para cada categoría, con `&volume=0..1`, para que el streamer oiga que el marcador se registró sin apartar la vista del juego. +- **Idiomas:** la interfaz está en inglés y en español, y `` sigue el idioma elegido para los lectores de pantalla. + +## Qué no se ha probado todavía + +- **Lectores de pantalla:** StreamCollab no se ha probado con NVDA, JAWS, VoiceOver ni el Narrador de Windows. +- **Navegadores:** los tests automáticos se ejecutan en Chromium (el motor de las docks y fuentes de navegador de OBS Studio). Firefox y Safari no se han probado. +- **OBS Studio:** el uso con teclado dentro de una dock de OBS depende de su navegador integrado y solo se ha probado a mano. + +## Limitaciones conocidas + +- **HUD:** la superposición que se usa como fuente de navegador en OBS no tiene controles interactivos a propósito; es una capa visual (y opcionalmente sonora) de tu emisión. +- **Contraste de color:** axe lo comprueba en las pantallas indicadas arriba. Otros estados (por ejemplo, un aviso del HUD sobre el vídeo de tu juego) dependen de lo que haya detrás. + +--- + +## Comentarios e informes + +Si algo de StreamCollab te resulta difícil o imposible de usar con tu configuración, cuéntanoslo: + +- **Correo:** [cristianguerrero1958@outlook.com](mailto:cristianguerrero1958@outlook.com) +- **GitHub Issues:** abre un [informe de error](https://github.com/Consterna/streamcollab/issues/new?template=bug_report.yml) y añade la etiqueta `accessibility`. + +Indica la vista (Dock, HUD o Panel del director), tu navegador o versión de OBS y la tecnología de apoyo que uses. diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md index a57f122..1a9d937 100644 --- a/ACCESSIBILITY.md +++ b/ACCESSIBILITY.md @@ -1,66 +1,53 @@ # Accessibility Statement for StreamCollab -**English** | [Español](docs/ACCESSIBILITY.es.md) +**English** | [Español](ACCESSIBILITY.es.md) -StreamCollab is committed to ensuring digital accessibility for all content creators, streamers, editors, and viewers, regardless of physical, sensory, or cognitive abilities. We continually improve the user experience for everyone and apply relevant accessibility standards, targeting conformance with the **Web Content Accessibility Guidelines (WCAG) 2.1 Level AA**. +StreamCollab aims for the **Web Content Accessibility Guidelines (WCAG) 2.1 Level AA** in the Dock, the Director Panel and the HUD. This is a goal, not a conformance claim: the project has not had an independent accessibility audit. This page says what is checked automatically, what has not been tested yet, and how to report a barrier. --- -## Accessibility Features in StreamCollab +## What is checked on every change -### 1. High-Contrast Studio Dock & HUD Overlay -- **Contrast Ratios:** The StreamCollab OBS Dock and Dashboard feature an ergonomic dark theme with contrast ratios meeting or exceeding 4.5:1 for normal body text and 3:1 for large typography, borders, and interactive UI controls. -- **Color-Blind Accessible Markers:** Marker categories (Clip, Funny, Death, Chapter) are differentiated by distinct geometric iconography, typography, and shape semantics, never relying solely on color hue to convey critical operational status (WCAG 1.4.1 Use of Color). -- **Reduced Motion:** Interactive animations and HUD toast transitions respect the user's operating system preferences (`prefers-reduced-motion: reduce`). +- **axe-core (WCAG 2.1 A and AA rules)** runs in the end-to-end suite ([`e2e/a11y.spec.ts`](e2e/a11y.spec.ts)) in English and Spanish on: + - the Dock, empty and with a marker and the OBS settings open, + - the Director Panel, its Start Session dialog and its Export dialog, + - the Director Panel with a member, markers and a marker being edited, + - the HUD preview and the live HUD showing a marker toast. -### 2. Comprehensive Keyboard Navigation & Hotkeys -- **Logical Tab Order:** All actionable buttons, inputs, toggles, and session history items are accessible via sequential keyboard navigation (`Tab` / `Shift+Tab`). -- **Visible Focus States:** Clear, high-contrast focus rings (`:focus-visible`) indicate current interactive element placement across all views. -- **Keyboard Shortcuts:** Quick-marking hotkeys and instant undo (`Ctrl+Z` / `Cmd+Z`) allow fast-paced live operation without mouse reliance. -- **Focus Traps & Escape Handlers:** Modals and dialogs trap keyboard focus to prevent background traversal and dismiss cleanly with the `Escape` key, restoring focus to the triggering element. + The build fails on any violation. Automated tools only find part of the WCAG issues (contrast, names, roles, structure); they cannot judge, for example, whether a label is meaningful. +- **Dialogs** (Export, Start Session) are tested in Chromium ([`packages/client/test/components.browser.test.tsx`](packages/client/test/components.browser.test.tsx)): focus moves into the dialog when it opens, `Tab` / `Shift+Tab` stay inside it, `Escape` closes it and focus returns to the button that opened it. +- **Narrow docks:** the end-to-end suite checks that every Dock control stays on screen at 300, 360 and 420 px wide. +- **Reflow (WCAG 1.4.10):** the end-to-end suite checks that the Director Panel, with a session running, needs no horizontal scrolling at 320 px wide. +- **Focus after Start / Stop Session:** when the Start or Stop button that had the focus is replaced, the new one receives it instead of the page, also when the session was started or stopped from another tab (end-to-end tests). -### 3. Semantic Markup & WAI-ARIA -- **HTML5 Landmarks:** User interfaces are structured with native landmarks (`
`, `