Compare commits

..

2 Commits

Author SHA1 Message Date
lebaudantoine f1d3799434 🔖(minor) bump release to 1.29.0 2026-08-25 23:22:29 +02:00
lebaudantoine e59aaaa998 📝(changelog) fix a minor changelog issue
Wrongly added to an old section during a rebase.
2026-08-25 23:14:16 +02:00
20 changed files with 24 additions and 104 deletions
+3 -1
View File
@@ -8,6 +8,8 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
## [1.29.0] - 2026-08-25
### Added ### Added
- ✨(any) let any authenticated user manage the lobby on trusted rooms - ✨(any) let any authenticated user manage the lobby on trusted rooms
@@ -24,6 +26,7 @@ and this project adheres to
- ⬆️(frontend) upgrade posthog-js from 1.404.1 to 1.409.5 - ⬆️(frontend) upgrade posthog-js from 1.404.1 to 1.409.5
- ⚡️(frontend) apply frugal constraint to the active meeting audio track - ⚡️(frontend) apply frugal constraint to the active meeting audio track
- ⚡️(backend) replace blocking Redis KEYS with cursor-based SCAN - ⚡️(backend) replace blocking Redis KEYS with cursor-based SCAN
- ✨(summary) add hostname to analytics properties
## [1.28.0] - 2026-08-24 ## [1.28.0] - 2026-08-24
@@ -62,7 +65,6 @@ and this project adheres to
- 🔥(frontend) drop unused vendored ConnectionObserver - 🔥(frontend) drop unused vendored ConnectionObserver
- 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines - 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines
- ✨(summary) add hostname to analytics properties
### Fixed ### Fixed
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "agents" name = "agents"
version = "1.28.0" version = "1.29.0"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"livekit-agents==1.6.7", "livekit-agents==1.6.7",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]] [[package]]
name = "agents" name = "agents"
version = "1.28.0" version = "1.29.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "livekit-agents" }, { name = "livekit-agents" },
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project] [project]
name = "meet" name = "meet"
version = "1.28.0" version = "1.29.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }] authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]] [[package]]
name = "meet" name = "meet"
version = "1.28.0" version = "1.29.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp" },
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "meet", "name": "meet",
"version": "1.28.0", "version": "1.29.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "meet", "name": "meet",
"version": "1.28.0", "version": "1.29.0",
"dependencies": { "dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0", "@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
"@fontsource-variable/lexend": "5.2.11", "@fontsource-variable/lexend": "5.2.11",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "meet", "name": "meet",
"private": true, "private": true,
"version": "1.28.0", "version": "1.29.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "panda codegen && vite", "dev": "panda codegen && vite",
@@ -42,23 +42,6 @@ export type IceCandidateReport = {
working: IceCandidatePair[] working: IceCandidatePair[]
} }
/**
* True when the selected pair does not carry media over plain UDP: the browser
* fell back to a TURN relay over TCP or TLS, or even the direct route is not
* UDP. Media still flows, but quality usually degrades under load.
*
* Accepts the loosely typed `data` stored on the step result; anything that is
* not an IceCandidateReport simply yields false.
*/
export const isSuboptimalRoute = (data: unknown): boolean => {
const selected = (data as IceCandidateReport | null | undefined)?.selected
if (!selected) return false
const transport = selected.local.relayProtocol ?? selected.local.protocol
// An unreported transport is not evidence of a bad route.
if (!transport) return false
return transport.toLowerCase() !== 'udp'
}
const PROBE_WIDTH = 320 const PROBE_WIDTH = 320
const PROBE_HEIGHT = 180 const PROBE_HEIGHT = 180
const PROBE_FPS = 15 const PROBE_FPS = 15
@@ -2,33 +2,18 @@ import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ProgressBar } from 'react-aria-components' import { ProgressBar } from 'react-aria-components'
import { css, cx } from '@/styled-system/css' import { css, cx } from '@/styled-system/css'
import { A } from '@/primitives'
import type { ConnectionTestStats } from '../types' import type { ConnectionTestStats } from '../types'
import { statusSquareClass } from './stepAppearance' import { statusSquareClass } from './stepAppearance'
/** type SummaryState = 'idle' | 'running' | 'passed' | 'partial' | 'failed'
* Network prerequisites (protocols, ports, TURN fallbacks), written for the
* reader's IT department rather than for the end user.
*/
const NETWORK_REQUIREMENTS_DOC_URL =
'https://docs.numerique.gouv.fr/docs/f2baa1b9-f29e-4d58-959d-65d4376fc6b8/'
type SummaryState = /** Only a failure earns a colour: everything else stays near-black. */
| 'idle'
| 'running'
| 'passed'
| 'partial'
| 'failed'
| 'warning'
/** Only a failure or a degraded route earns a colour: everything else stays near-black. */
const stateColorClass: Record<SummaryState, string> = { const stateColorClass: Record<SummaryState, string> = {
idle: css({ color: 'greyscale.1000' }), idle: css({ color: 'greyscale.1000' }),
running: css({ color: 'greyscale.1000' }), running: css({ color: 'greyscale.1000' }),
passed: css({ color: 'greyscale.1000' }), passed: css({ color: 'greyscale.1000' }),
partial: css({ color: 'greyscale.1000' }), partial: css({ color: 'greyscale.1000' }),
failed: css({ color: 'danger.600' }), failed: css({ color: 'danger.600' }),
warning: css({ color: 'warning' }),
} }
const cardClass = css({ const cardClass = css({
@@ -191,31 +176,23 @@ const Counter = ({
export const ConnectionTestSummary = ({ export const ConnectionTestSummary = ({
stats, stats,
isRunning, isRunning,
routeWarning = false,
children, children,
}: { }: {
stats: ConnectionTestStats stats: ConnectionTestStats
isRunning: boolean isRunning: boolean
/** The selected ICE route is usable but not UDP (e.g. TURN over TCP/TLS). */
routeWarning?: boolean
children?: ReactNode children?: ReactNode
}) => { }) => {
const { t } = useTranslation('connectionTest') const { t } = useTranslation('connectionTest')
// A hard failure still outranks the route warning; the warning outranks
// 'partial' because a measured degraded route matters more than skipped
// camera or microphone checks.
const state: SummaryState = isRunning const state: SummaryState = isRunning
? 'running' ? 'running'
: !stats.hasStarted : !stats.hasStarted
? 'idle' ? 'idle'
: stats.failed > 0 : stats.failed > 0
? 'failed' ? 'failed'
: routeWarning : stats.skipped > 0
? 'warning' ? 'partial'
: stats.skipped > 0 : 'passed'
? 'partial'
: 'passed'
return ( return (
<section className={cardClass}> <section className={cardClass}>
@@ -229,22 +206,7 @@ export const ConnectionTestSummary = ({
: t(`summary.${state}`)} : t(`summary.${state}`)}
</p> </p>
<p className={hintClass}> <p className={hintClass}>{t(`summary.${state}Hint`)}</p>
{t(`summary.${state}Hint`)}
{state === 'warning' && (
<>
{' '}
<A
href={NETWORK_REQUIREMENTS_DOC_URL}
target="_blank"
rel="noreferrer"
size="sm"
>
{t('summary.warningDocLink')}
</A>
</>
)}
</p>
</div> </div>
{stats.hasStarted && ( {stats.hasStarted && (
@@ -53,9 +53,6 @@ const fromCheckInfo = (info: CheckInfo): Partial<ConnectionTestStepResult> => ({
status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed', status: CHECK_STATUS_TO_STEP[info.status] ?? 'failed',
summary: info.description, summary: info.description,
logs: info.logs, logs: info.logs,
// Only SelectedCandidateCheck sets `data` (the ICE candidate report); for
// the stock LiveKit checks it is undefined and this is a no-op.
data: info.data as Record<string, unknown> | undefined,
}) })
const groupDevicesByKind = (devices: MediaDeviceInfo[]) => { const groupDevicesByKind = (devices: MediaDeviceInfo[]) => {
@@ -13,7 +13,6 @@ import { css } from '@/styled-system/css'
import { Center, VStack } from '@/styled-system/jsx' import { Center, VStack } from '@/styled-system/jsx'
import { Permissions } from '@/features/rooms/components/Permissions' import { Permissions } from '@/features/rooms/components/Permissions'
import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner' import { useConnectionTestRunner } from '../hooks/useConnectionTestRunner'
import { isSuboptimalRoute } from '../checks/selectedCandidate'
import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow' import { ConnectionTestStepRow } from '../components/ConnectionTestStepRow'
import { ConnectionTestSummary } from '../components/ConnectionTestSummary' import { ConnectionTestSummary } from '../components/ConnectionTestSummary'
import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types' import { CONNECTION_TEST_GROUPS, summarizeSteps } from '../types'
@@ -71,13 +70,6 @@ const ConnectionTest = () => {
() => new Map(steps.map((step) => [step.id, step] as const)), () => new Map(steps.map((step) => [step.id, step] as const)),
[steps] [steps]
) )
// The summary headline downgrades to a warning when media flows but over a
// fallback route: TURN over TCP/TLS, or anything else that is not UDP.
const routeWarning = useMemo(() => {
const step = stepsById.get('selectedCandidate')
return step?.status === 'success' && isSuboptimalRoute(step.data)
}, [stepsById])
const isPublishVideoRunning = const isPublishVideoRunning =
stepsById.get('publishVideo')?.status === 'running' stepsById.get('publishVideo')?.status === 'running'
@@ -107,11 +99,7 @@ const ConnectionTest = () => {
<CenteredContent withBackButton> <CenteredContent withBackButton>
<Center> <Center>
<VStack gap="1.5rem" maxWidth="40rem" width="100%"> <VStack gap="1.5rem" maxWidth="40rem" width="100%">
<ConnectionTestSummary <ConnectionTestSummary stats={stats} isRunning={isRunning}>
stats={stats}
isRunning={isRunning}
routeWarning={routeWarning}
>
{isRunning ? ( {isRunning ? (
// A disabled "run" button while the test runs is dead weight: // A disabled "run" button while the test runs is dead weight:
// cancelling is the only thing left to do. // cancelling is the only thing left to do.
@@ -46,9 +46,6 @@
"passedHint": "Ihr Browser, Ihre Geräte und Ihr Netzwerk sind für eine Besprechung bereit.", "passedHint": "Ihr Browser, Ihre Geräte und Ihr Netzwerk sind für eine Besprechung bereit.",
"partial": "Teilweiser Test", "partial": "Teilweiser Test",
"partialHint": "Einige Prüfungen wurden übersprungen. Erlauben Sie den Zugriff auf Ihre Kamera und Ihr Mikrofon, um diese zu testen.", "partialHint": "Einige Prüfungen wurden übersprungen. Erlauben Sie den Zugriff auf Ihre Kamera und Ihr Mikrofon, um diese zu testen.",
"warning": "Verbindung nicht optimal",
"warningHint": "Achtung: Sie befinden sich nicht in optimalen Bedingungen für die Nutzung des Tools. Ihre Medien werden nicht über UDP übertragen (Rückgriff auf ein TURN-Relay über TCP oder TLS), was die Audio- und Videoqualität beeinträchtigen kann.",
"warningDocLink": "Technische Dokumentation für Ihre IT-Abteilung",
"failed_one": "{{count}} Prüfung fehlgeschlagen", "failed_one": "{{count}} Prüfung fehlgeschlagen",
"failed_other": "{{count}} Prüfungen fehlgeschlagen", "failed_other": "{{count}} Prüfungen fehlgeschlagen",
"failedHint": "Öffnen Sie die fehlgeschlagenen Prüfungen für weitere Details und senden Sie den Bericht an Ihre IT-Abteilung." "failedHint": "Öffnen Sie die fehlgeschlagenen Prüfungen für weitere Details und senden Sie den Bericht an Ihre IT-Abteilung."
@@ -46,9 +46,6 @@
"passedHint": "Your browser, your devices and your network are ready for a meeting.", "passedHint": "Your browser, your devices and your network are ready for a meeting.",
"partial": "Partially tested", "partial": "Partially tested",
"partialHint": "Some checks were skipped. Allow access to your camera and microphone to test them.", "partialHint": "Some checks were skipped. Allow access to your camera and microphone to test them.",
"warning": "Suboptimal connection",
"warningHint": "Warning: you are not in optimal conditions to use the tool. Your media is not carried over UDP (it falls back to a TURN relay over TCP or TLS), which can degrade audio and video quality.",
"warningDocLink": "Technical documentation for your IT department",
"failed_one": "{{count}} check failed", "failed_one": "{{count}} check failed",
"failed_other": "{{count}} checks failed", "failed_other": "{{count}} checks failed",
"failedHint": "Open the failed checks below for details, then send the report to your IT department." "failedHint": "Open the failed checks below for details, then send the report to your IT department."
@@ -46,9 +46,6 @@
"passedHint": "Votre navigateur, vos périphériques et votre réseau sont prêts pour une réunion.", "passedHint": "Votre navigateur, vos périphériques et votre réseau sont prêts pour une réunion.",
"partial": "Test partiel", "partial": "Test partiel",
"partialHint": "Certaines vérifications ont été ignorées. Autorisez l'accès à votre caméra et à votre microphone pour les tester.", "partialHint": "Certaines vérifications ont été ignorées. Autorisez l'accès à votre caméra et à votre microphone pour les tester.",
"warning": "Connexion non optimale",
"warningHint": "Attention : vous n'êtes pas dans les conditions optimales pour accéder à l'outil. Vos flux ne transitent pas en UDP (repli sur un relais TURN en TCP ou TLS), ce qui peut dégrader la qualité audio et vidéo.",
"warningDocLink": "Documentation technique à destination de votre service informatique",
"failed_one": "{{count}} vérification en échec", "failed_one": "{{count}} vérification en échec",
"failed_other": "{{count}} vérifications en échec", "failed_other": "{{count}} vérifications en échec",
"failedHint": "Ouvrez les vérifications en échec pour voir le détail, puis transmettez le rapport à votre service informatique." "failedHint": "Ouvrez les vérifications en échec pour voir le détail, puis transmettez le rapport à votre service informatique."
@@ -46,9 +46,6 @@
"passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.", "passedHint": "Je browser, apparaten en netwerk zijn klaar voor een vergadering.",
"partial": "Gedeeltelijke test", "partial": "Gedeeltelijke test",
"partialHint": "Sommige controles zijn overgeslagen. Geef toegang tot je camera en microfoon om deze te testen.", "partialHint": "Sommige controles zijn overgeslagen. Geef toegang tot je camera en microfoon om deze te testen.",
"warning": "Verbinding niet optimaal",
"warningHint": "Let op: je bevindt je niet in optimale omstandigheden om de tool te gebruiken. Je media loopt niet via UDP (terugval op een TURN-relay via TCP of TLS), wat de audio- en videokwaliteit kan verminderen.",
"warningDocLink": "Technische documentatie voor je IT-afdeling",
"failed_one": "{{count}} controle mislukt", "failed_one": "{{count}} controle mislukt",
"failed_other": "{{count}} controles mislukt", "failed_other": "{{count}} controles mislukt",
"failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling." "failedHint": "Open de mislukte controles voor meer details en stuur het rapport door naar je IT-afdeling."
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.28.0", "version": "1.29.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.28.0", "version": "1.29.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@html-to/text-cli": "0.6.1", "@html-to/text-cli": "0.6.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.28.0", "version": "1.29.0",
"description": "An util to generate html and text django's templates from mjml templates", "description": "An util to generate html and text django's templates from mjml templates",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.28.0", "version": "1.29.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sdk", "name": "sdk",
"version": "1.28.0", "version": "1.29.0",
"license": "ISC", "license": "ISC",
"workspaces": [ "workspaces": [
"./library", "./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.28.0", "version": "1.29.0",
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "summary" name = "summary"
version = "1.28.0" version = "1.29.0"
dependencies = [ dependencies = [
"fastapi[standard]>=0.105.0", "fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0", "uvicorn>=0.24.0",