0.13.0
2026-09-03Nouveautés
Le Kit de marque et le Thème sortent du ventre d'une vidéo pour devenir deux éléments distincts dans la barre latérale. Avant cette version, modifier un Kit de marque obligeait à ouvrir ou créer une vidéo, entrer dans le Studio Editor, passer à l'onglet « Vidéo entière », puis faire défiler jusqu'à la carte Thème — quatre étapes pour plonger dans une seule vidéo afin de modifier quelque chose de partagé par toutes les vidéos. Et comme un kit ne pouvait être généré qu'en capturant les couleurs du storyboard actuellement ouvert, pas de vidéo, pas de kit. Désormais, il s'ouvre directement depuis la barre latérale, et vous pouvez créer un kit à partir de zéro.
Un kit ne conserve plus ses propres couleurs — il choisit un Thème. Deux notions autrefois confondues se séparent nettement : un Thème, ce sont deux couleurs, une police et une recette d'étalonnage des rushes ; un Kit de marque, c'est une identité — logo, nom de marque, badge, voix, style de sous-titres. Avec cela, les 24 thèmes prédéfinis passent d'un aboutissement à un point de départ : on duplique puis on modifie, l'original reste intact (un ancien brouillon qui a déjà figé un thème donné doit continuer à s'afficher exactement dans cette couleur pour toujours, donc pas question de l'écraser). Et pour la première fois, votre propre palette est une chose qu'on peut enregistrer — auparavant, garder ses propres couleurs n'offrait que deux options : les ressaisir sur chaque vidéo, ou les empaqueter dans un « kit » et accepter qu'à partir de là, « kit » signifie « thème ».
L'aperçu est une image réelle, pas une idée qu'on s'en fait. Il passe par le pipeline de construction exact et le gabarit exact utilisés au moment de l'export d'une vidéo, donc la couleur, la police et le logo que vous voyez sont ceux que vous obtiendrez. Trois échantillons, pas un seul, car un seul n'en dit pas assez : l'étalonnage des rushes est un filtre posé sur la vidéo de fond — sans rushes, il n'y a rien à voir ; et savoir si les sous-titres restent lisibles sur un B-roll ne peut se vérifier sans B-roll.
Quand une vidéo dérive de son kit, l'application le signale et ouvre les deux chemins de retour. Appliquer un kit puis retoucher une couleur rend la vidéo silencieusement différente du kit même qu'elle revendique — auparavant, rien ne le signalait, et aucun chemin n'existait dans un sens ni dans l'autre : un kit ne pouvait pas être mis à jour depuis une vidéo, et une vidéo ne pouvait pas être repeinte selon son kit. Il y a désormais un bandeau d'avertissement avec deux choix : mettre à jour le kit à partir de cette vidéo (s'applique aux vidéos suivantes, et échappe à l'Annuler) ou enregistrer comme nouveau kit. Les 24 thèmes prédéfinis n'ont accès qu'à la seconde option, précisément pour la raison ci-dessus.
L'application a désormais des comptes : à partir de cette version, la connexion Google est obligatoire. L'écran d'accueil initial ajoute une étape de connexion et un court questionnaire — êtes-vous une boutique, un créateur ou une agence, quel secteur, avez-vous déjà une clé LLM. Les personnes qui mettent à jour une ancienne version y passeront aussi : « Connecté avec Google » est désormais un élément obligatoire de la liste de vérification de l'écran Créer. Le compte s'accompagne de trois événements envoyés au serveur — vidéo créée, vidéo exportée, erreur de rendu — et pour les erreurs de rendu en particulier, seul le nom du type d'erreur est envoyé, jamais le texte du message, car ce texte contient souvent un chemin de votre propre machine.
Les vidéos exportées avec le forfait Free portent un filigrane 9Video dans le coin. L'aperçu dans l'application n'en a pas — la marque n'est apposée qu'à l'export. Réglages → Forfait indique sur quel forfait vous êtes et propose un bouton pour demander une mise à niveau afin de désactiver le filigrane ; les comptes Pro/Agency arborent en plus leur propre badge dans la barre latérale.
La version précédente promettait des chiffres pour la prochaine fois. Les voici. Mesuré à partir des journaux de l'application elle-même — 184 échecs de construction de scène — le contraste en représente 106 (58%), plus du double de la deuxième cause la plus fréquente ; et sur 338 erreurs de contraste, 153 étaient sous 1.6:1, c'est-à-dire un texte pratiquement invisible plutôt qu'un contraste presque suffisant. Trois causes racines ont été corrigées dans cette version :
- Le message renvoyé à l'IA nomme désormais ses deux propres couleurs. Auparavant, elle ne recevait qu'un « contraste 2.33:1 » accompagné d'un chemin CSS — aucun des deux ne mentionnait une couleur, donc l'IA devait deviner laquelle de ses propres déclarations produisait ce ratio, et elle épuisait généralement tous ses essais sans y parvenir.
- Une barre de progression dessinée à largeur zéro puis agrandie via scaleX reste invisible pour toujours — zéro multiplié par n'importe quoi reste zéro, et elle était invisible jusque dans le fichier mp4 exporté lui-même, pas seulement à la prévisualisation. Dans la bibliothèque de brouillons, 29 scènes sur 308 portaient ce bug — restreint aux 71 scènes les plus récentes, il n'en restait que 5. L'application le détecte désormais directement dans le code source de la scène, sans passer par un aller-retour dans le navigateur, et indique immédiatement une version qui fonctionne.
- Un guillemet double imbriqué dans un attribut style tronquait toute une déclaration de couleur : toutes les polices fournies par l'application, sauf Inter, ont un espace dans leur nom, donc l'IA écrivait
font: 600 26px "Playfair Display"comme partout ailleurs — et l'attribut HTML se terminait juste à ce guillemet. Sur une vidéo réelle, une scène a perdu la couleur de son texte puis a échoué avec « contraste 1.39:1 » trois fois de suite. L'application corrige désormais cela juste avant la construction, si bien que les brouillons déjà enregistrés sont eux aussi sauvés, pas seulement les scènes nouvellement générées.
Un rappel de plus pour l'IA : le z-index ne peut pas négocier un espace trop étroit. Une scène réelle plaçait trois cartes secondaires de 280px de large à côté d'une carte principale de 900px dans un cadre horizontal de 1080px — il n'y avait jamais assez de place, si bien qu'elles recouvraient toujours la carte principale et enterraient le titre. Trois cycles de réécriture n'ont fait que tourner le z-index et changer les couleurs, parce que rien ne lui avait dit que le z-index décide seulement qui se trouve au-dessus une fois le chevauchement déjà là — il ne l'empêche pas de se produire.
La narration en vietnamien mêle moins d'anglais. La voix vi-VN d'edge-tts prononce mal les mots anglais placés au milieu d'une phrase. Une correction par SSML a été tentée et a carrément cassé la synthèse sur le point de terminaison gratuit utilisé par l'application, donc cette fois la seule voie qui a fonctionné a été de le préciser directement dans l'invite d'écriture du script.
Quelques éléments plus petits mais fréquemment rencontrés. Le contenu en cours de rédaction sur l'écran Créer ne se perd plus lorsque vous passez sur la page Kit de marque puis revenez — sujet, format, durée, voix, kit, recette, fichiers joints, tout reste intact ; c'est la page Kit de marque qui vous a invité à ce détour, c'est donc à elle d'assumer le retour. Deux fenêtres 9Video ne tournent plus en parallèle sur le même profil — auparavant, deux processus partageaient un seul settings.json (clés API, Kit de marque, thème), le dernier à écrire l'emportait, et aucune précaution interne à l'application ne pouvait vous en sauver. Une connexion bloquée expire désormais au bout de 90 secondes au lieu de rester coincée indéfiniment sur un écran d'accueil qui n'a pas de bouton pour passer. Et la boîte de dialogue même que vous êtes en train de lire est nouvelle : après chaque mise à jour, l'application raconte les nouveautés, et vous pouvez la revoir depuis les Réglages.
Encore non mesuré : combien de cycles de re-rendu les trois correctifs de scène ci-dessus permettent réellement d'économiser sur de vraies vidéos. Les chiffres ci-dessus disent à quel point chaque bug était fréquent, pas encore combien le correctif en récupère — c'est une question laissée pour la prochaine version.
Features
- brand-kit: make a kit creatable without a video, and divergence detectable (db99503)
- brand: Brand Kit form picks a Theme instead of raw colour/font fields (d720011)
- brand: build the three preview samples out of the real render templates (5f9df7a)
- brand: edit a kit next to the frame it produces (11e5296)
- brand: give brand kits and themes a place of their own in the app (e80de4b)
- brand: give brand kits and themes one owner in the renderer (ffd5977)
- brand: let a chosen theme reach the first build, not just the editor (f49fddd)
- brand: make the 24 built-in themes a starting point instead of a dead end (94c2eb5)
- brand: preview a kit or theme as the frame it actually renders as (57cb6ce)
- desktop: add AuthController for Google login state machine (c76af20)
- desktop: add LoginStep component and i18n copy (705aae8)
- desktop: add me:upgradeWaitlist IPC handler (c3aaf0f)
- desktop: add plan section to Settings (18b0852)
- desktop: add plan to AppSettings (0bf76f6)
- desktop: add Plan type and shouldWatermark fail-closed gate (c4529de)
- desktop: add renderWatermarkOverlay (8b82d13)
- desktop: add required googleAuth readiness check (188038a)
- desktop: add serverClient — getProfile/completeOnboarding/recordEvent (34270d7)
- desktop: add SurveyStep component and i18n copy (13f9615)
- desktop: add syncPlanStatus (7acb034)
- desktop: add telemetry event names and extend onboarding to 5 steps (adb920f)
- desktop: add UserProfile.plan and requestUpgradeWaitlist() (bf5bd82)
- desktop: dev-only skip for onboarding's login+survey steps (f5fb742)
- desktop: empaqueter CHANGELOG.md et l'exposer via IPC (1002fb0)
- desktop: expose auth/onboarding-survey IPC methods on window.api (9553512)
- desktop: expose plan status + upgrade waitlist on window.api (7f8a728)
- desktop: gate the watermark on plan at render time (4483a18)
- desktop: ajouter une fenêtre modale Nouveautés affichée après une mise à jour (5827ff8)
- desktop: name the failing token pairing in the re-author hint (cdc130d)
- desktop: analyseur pour CHANGELOG.md et règles d'affichage de la fenêtre modale Nouveautés (c8da8be)
- desktop: record video_created/video_exported/render_error telemetry (38fe2a5)
- desktop: register ninevideo:// protocol, wire AuthController and auth/onboarding IPC (cc5d3c1)
- desktop: show a plan badge in the sidebar for pro/agency (580337a)
- desktop: sync plan on login/startup, close settings:save plan hole (4669ce1)
- desktop: wire mandatory login + survey steps into onboarding flow (945d090)
- editor: say when a video has drifted from its kit, and offer both ways out (38b19fe)
- nav: route Brand Kit and Theme to separate pages (9bdc00f)
- nav: split the "Brand" rail item into Brand Kit and Theme (ef698d6)
- server: add activation rate, DAU, and D7 retention KPI queries (4592cd1)
- server: add AdvanceDripStep command handler with catch-up semantics (889a099)
- server: add BullMQ drip-emails queue and processor (6874295)
- server: add CaptureLead command and POST /leads for landing-page capture (f632afa)
- server: add CompleteOnboarding command handler and OnboardingCompletedEvent (fa5e852)
- server: add DripCampaign aggregate with D0/D3/D7/D14/D21 schedule logic (efd417a)
- server: add GET /telemetry/kpis (activation, DAU, D7 retention) (3fcf06e)
- server: add GetUserProfile query handler (7f5206c)
- server: add Google OAuth sign-in, JWT auth guard, and /me endpoints (2b0c610)
- server: add grant-plan manual plan-grant script (61c30dd)
- server: add hourly cron scan that enqueues drip campaigns with a step due (1977ef9)
- server: add IDripCampaignRepository port, MikroORM adapter, and drip_campaign migration (b83820c)
- server: add ITelemetryEventRepository port, MikroORM adapter, and telemetry_event migration (1686e18)
- server: add IUserRepository port, MikroORM adapter, and users table migration (dceee8d)
- server: add Lead aggregate, repository, and migration (df9fd23)
- server: add Lead.userId and the upgrade_waitlist source (759aa0c)
- server: add MailPort, Resend adapter, and drip email templates (1e18a7f)
- server: add plan field and grantPlan() to User (3c99d13)
- server: add POST /me/upgrade-waitlist (210a941)
- server: add RecordEvent command handler and UserSignedUp-to-telemetry subscriber (e7b7883)
- server: add shared-kernel Result type and Email value object (71d761e)
- server: add SignInWithGoogle command handler and UserSignedUpEvent (7881872)
- server: add TelemetryEvent aggregate and event-name enum (976d3f9)
- server: add User aggregate and OnboardingSurvey to Identity domain (bac19c3)
- server: capture a Lead when UpgradeWaitlistRequestedEvent fires (d0db899)
- server: CaptureLeadCommand accepts and backfills userId (2192c21)
- server: compose IdentityModule and register it in AppModule (8886833)
- server: compose TelemetryModule and add POST /telemetry/events (c33750d)
- server: enable CORS for POST /leads and graceful shutdown hooks (695f8b8)
- server: migrate users table for plan column (6c5fa11)
- server: RequestUpgradeWaitlistCommand publishes UpgradeWaitlistRequestedEvent (4338068)
- server: return plan from GET /me (0a170a5)
- server: scaffold NestJS app with health endpoint (d9dc214)
- server: start DripCampaign on OnboardingCompleted and convert Lead on UserSignedUp (e246354)
- server: validate required env vars with Joi at boot (3d209e3)
- server: wire MikroORM + Postgres/Redis docker-compose, add DB health check (4f0dadf)
- settings: persist the user's own themes; delete a field that documents a guard that isn't there (af8b236)
- theme: let a user's own palette be a saved thing, not a per-video re-edit (2ec0d3f)
- theme: stop the theme matcher from claiming a preset it only coincides with (7e3ce8d)
Bug Fixes
- add server/.env.example and unignore .env.example pattern (c145c20)
- auth: suppress orphaned-promise rejection, guard getStatus() decrypt throw (8fd485f)
- brand-kit,theme: add fallback names when sanitized name is blank (abb465d)
- brand-kit: clear meta.brandKitId when a theme or preset is applied over a kit (5d3b7c1)
- brand-kit: strip a stale kit id from drafts written before the source fix (d51679b)
- brand: close final-review findings — persist migration, warn on theme delete (5fca335)
- brand: close the pre-merge review's six findings on the Brand Kit page (da664c2)
- brand: compute Brand Kit saves against current state, not a stale render closure (ab7d59e)
- brand: re-measure preview on resize, fit the page to the viewport (997d53a)
- brand: revert the preset picker to a list, make side columns responsive (3618659)
- brand: stack preview+form below a container-query breakpoint (b237190)
- brand: stop clipping the right column and match the editor's inspector polish (cd7de58)
- brand: stop the kit branch from outliving a theme/preset applied over it (63e8265)
- build: make SERVER_URL genuinely build-time overridable (5ede57a)
- build: register ninevideo:// URL scheme in packaged builds (e32ad26)
- clarify telemetry error handling in comment (6535ddc)
- composer: reframe the Brand Kit chip as "Diện mạo" (Look), not identity (4f9c983)
- create: drop the colour preview from the Recipe chip entirely (8870a29)
- create: make the Brand Kit page's theme pick one-shot (689cd48)
- create: preserve staged composer state across Create navigation (72fc280)
- create: resolve the Brand Kit chip's swatch through the kit's theme (6cc587b)
- create: tint the recipe preview swatches with the active Brand Kit (66a5735)
- crm: establish MikroORM RequestContext for cron/queue call sites (5b9a135)
- crm: prevent BullMQ unhandled 'error' crashes in drip-emails queue (1c07b3a)
- crm: retry drip-emails jobs with exponential backoff (b747dc4)
- desktop,server: guard cold-start getSecret, fix TDZ, unify plan gate, add upgrade-waitlist e2e coverage (48508a8)
- desktop: add missing create.readiness.items.googleAuth i18n copy (0d41dc6)
- desktop: address final-review nits on the Vietnamese TTS prompt tweak (abc49dc)
- desktop: enlarge free-tier watermark overlay for visibility (d34c56b)
- desktop: fail a tween aimed at an element the html gives no size (d3fdcb5)
- desktop: keep DevTools closed by default in dev (a52a72c)
- desktop: accepter les titres à tiret cadratin dans CHANGELOG.md et supprimer l'entrée la plus récente codée en dur dans le test (ebf2b63)
- desktop: nudge Vietnamese narration away from unnecessary English words (90a952b)
- desktop: repair a double quote nested in an inline style attribute (f3de263)
- desktop: scale watermark overlay to match render resolution (ad93da2)
- desktop: stop render_error telemetry from leaking local paths (da7a7c5)
- desktop: tell the scene author z-index cannot fix an overlap (a4bdc07)
- desktop: time out a hung Google login attempt (3592970)
- desktop: update AuthController comment and test fixtures to the corrected scheme (9e34fde)
- desktop: wrap auth/onboarding-survey IPC errors in AppError (36cd76f)
- editor: extract brand-drift arbitration into a tested divergenceState (6be89d9)
- editor: saveVideoAsKit reads the freshly-built themes list, not a stale closure (4ffe653)
- editor: update InspectorVideo's kit/theme call sites for themeId kits (b330e58)
- generate: resolve a selected Brand Kit's colours via its linked theme (71c8379)
- main: add Electron's single-instance lock (9777129)
- main: guard recordTelemetry sync getSecret throw (2c61bc6)
- replace convoluted type with simple TestingModule annotation (8b48c14)
- server: encapsulate onboarding field and validate email in User entity (a572b26)
- server: fix docker-compose api connectivity to postgres/redis (d1b0e2e)
- server: map domain errors to HTTP status codes (ec4cba0)
- server: pin exact versions and add ESLint config (f7ab2cd)
- server: restore full typecheck coverage for api-frontend and shared-kernel (6dd412a)
- server: run migrations against the built dist output, not TS sources (674d172)
- server: type QueryBus.execute and log errors in event subscribers (378683e)
- server: use a syntactically valid custom URL scheme for the OAuth callback (7d651f5)
- settings: migrate legacy colour-owning brand kits to themeId references (40026f0)
Performance Improvements
- telemetry: add indexes on telemetry_event for KPI queries (4a138f6)