0.13.0
2026-09-03Novedades
Brand Kit y Theme salen de las entrañas de un vídeo y pasan a ser dos elementos propios en la barra lateral. Antes de esta versión, para editar un Brand Kit había que abrir o crear un vídeo, entrar en el Studio Editor, ir a la pestaña «Todo el vídeo» y desplazarse hasta la tarjeta Theme — cuatro pasos para sumergirse dentro de un vídeo solo para cambiar algo que comparten todos los vídeos. Y como un kit solo podía generarse capturando los colores del storyboard que estuviera abierto en ese momento, sin vídeo no se podía crear un kit. Ahora se abre directamente desde la barra lateral, y puedes crear un kit desde cero.
El kit ya no guarda sus propios colores: elige un Theme. Dos conceptos que antes estaban mezclados ahora se separan por completo: un Theme son dos colores, una tipografía y una receta de gradación de color para el material; un Brand Kit es una identidad — logo, nombre de marca, insignia, voz, estilo de subtítulos. Junto con esto, los 24 temas predefinidos dejan de ser un punto final para convertirse en un punto de partida: se duplican y se editan, y el original queda intacto (un borrador antiguo que ya quedó marcado con un tema determinado debe seguir mostrando exactamente ese color para siempre, así que no se puede sobrescribir). Y por primera vez, tu propia paleta es algo que se puede guardar — antes, conservar tus propios colores solo tenía dos caminos: volver a escribirlos en cada vídeo, o empaquetarlos en un «kit» y aceptar que, a partir de ahí, «kit» pasaba a significar «theme».
La vista previa es un fotograma real, no una idea aproximada de él. Pasa por el mismo proceso de construcción y la misma plantilla que se usan al exportar un vídeo de verdad, así que el color, la tipografía y el logo que ves son los que obtendrás. Tres muestras, no una sola, porque una muestra no dice lo suficiente: la gradación de color del material es un filtro que se aplica sobre el vídeo de fondo — sin material no hay nada que ver; y si los subtítulos siguen siendo legibles sobre un B-roll es algo que solo se puede responder con un B-roll de por medio.
Cuando un vídeo se desvía de su kit, la aplicación lo dice y abre las dos vías de vuelta. Aplicar un kit y luego ajustar un color deja al vídeo silenciosamente distinto del propio kit que dice usar — antes nada lo avisaba, y tampoco había ningún camino en ninguna dirección: el kit no se podía actualizar desde un vídeo, ni el vídeo se podía repintar según el kit. Ahora hay una franja de aviso con dos opciones: actualizar el kit a partir de este vídeo (se aplica a los vídeos siguientes, y queda fuera de Deshacer) o guardar como un kit nuevo. Los 24 temas predefinidos solo tienen la segunda opción, precisamente por la razón anterior.
La aplicación ahora tiene cuentas: a partir de esta versión, iniciar sesión con Google es obligatorio. La pantalla de incorporación inicial añade un paso de inicio de sesión y una breve encuesta — si eres una tienda, un creador o una agencia, de qué sector, si ya tienes una clave de LLM. Quienes actualicen desde una versión anterior también pasarán por esto: «Sesión iniciada con Google» ahora es un elemento obligatorio en la lista de comprobación de la pantalla Crear. Junto con la cuenta llegan tres eventos que se envían al servidor — vídeo creado, vídeo exportado, error de renderizado — y, en concreto para los errores de renderizado, solo se envía el nombre del tipo de error, nunca el texto del mensaje, porque ese texto suele llevar consigo una ruta de tu propio equipo.
Los vídeos exportados en el plan Free llevan una marca de agua de 9Video en la esquina. La vista previa dentro de la aplicación no la lleva — la marca solo se estampa al exportar. Ajustes → Plan indica en qué plan estás y tiene un botón para solicitar una mejora de plan y así desactivar la marca de agua; las cuentas Pro/Agency, además, lucen su propia insignia en la barra lateral.
La versión anterior prometió cifras para la próxima vez. Aquí están. Medido a partir de los propios registros de la aplicación — 184 construcciones de escena fallidas — el contraste representa 106 de ellas (58%), más del doble que la segunda causa más común; y de 338 errores de contraste, 153 quedaron por debajo de 1.6:1, es decir, texto prácticamente invisible y no un contraste que casi llegaba. Se corrigieron tres causas raíz en esta versión:
- El mensaje que se devuelve a la IA ahora llama por su nombre a sus propios dos colores. Antes solo recibía «contraste 2.33:1» junto con una ruta CSS — ninguno de los dos mencionaba un color, así que la IA tenía que adivinar cuál de sus propias declaraciones producía ese ratio, y normalmente agotaba todos sus intentos sin conseguirlo.
- Una barra de progreso dibujada con ancho cero y luego ampliada con scaleX se queda invisible para siempre — cero multiplicado por lo que sea sigue siendo cero, y era invisible incluso en el propio archivo mp4 exportado, no solo en la vista previa. En la biblioteca de borradores, 29 de 308 escenas tenían este error — limitado a las 71 escenas más recientes, eran 5. Ahora la aplicación lo detecta directamente en el código fuente de la escena, sin gastar una vuelta abriendo el navegador, y señala de inmediato una forma de escribirlo que sí funciona.
- Una comilla doble anidada dentro de un atributo style cortaba en seco toda una declaración de color: todas las tipografías que ofrece la aplicación salvo Inter tienen un espacio en el nombre, así que la IA escribía
font: 600 26px "Playfair Display"tal como se escribe en cualquier otro sitio — y el atributo HTML terminaba justo en esa comilla. En un vídeo real, una escena perdió el color del texto y luego falló con «contraste 1.39:1» tres veces seguidas. La aplicación ahora corrige esto justo antes de construir, así que los borradores ya guardados también quedan a salvo, no solo las escenas generadas de nuevo.
Un aviso más para la IA: z-index no puede resolver un espacio demasiado justo. Una escena real colocaba tres tarjetas secundarias de 280px de ancho junto a una tarjeta principal de 900px dentro de un marco horizontal de 1080px — nunca iba a haber sitio suficiente, así que siempre acababan tapando la tarjeta principal y enterrando el título. Tres rondas de reescritura solo movieron el z-index y cambiaron colores, porque nadie le había dicho que el z-index solo decide quién queda encima cuando ya hay superposición — no evita que esa superposición ocurra.
La narración en vietnamita mezcla menos inglés. La voz vi-VN de edge-tts pronuncia mal las palabras en inglés que aparecen en medio de una frase. Se probó una solución con SSML y esta rompió por completo la síntesis en el endpoint gratuito que usa la aplicación, así que esta vez el único camino que funcionó fue indicarlo directamente en el prompt de escritura del guion.
Algunas cosas más pequeñas pero con las que uno se topa a menudo. El contenido que se está redactando en la pantalla Crear ya no se pierde al pasar a la página de Brand Kit y volver — tema, proporción, duración, voz, kit, receta y archivos adjuntos quedan todos intactos; es la página de Brand Kit la que te invitó a ese viaje, así que es ella la responsable del regreso. Dos ventanas de 9Video ya no se ejecutan en paralelo en el mismo perfil — antes, dos procesos compartían un mismo settings.json (claves de API, Brand Kit, theme), ganaba quien escribiera último, y ningún cuidado dentro de la aplicación podía salvarte de eso. Un inicio de sesión colgado ahora expira a los 90 segundos en lugar de quedarse atascado para siempre en una pantalla de incorporación que no tiene botón para omitir. Y el propio cuadro de diálogo que estás leyendo ahora es nuevo: después de cada actualización cuenta las novedades, y se puede volver a consultar desde Ajustes.
Todavía sin medir: cuántas rondas de re-renderizado ahorran realmente en vídeos reales las tres correcciones de escena anteriores. Las cifras de arriba dicen cuán frecuente era cada error, no cuánto recupera aún la corrección — esa es una pregunta que queda para la próxima versión.
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: empaquetar CHANGELOG.md y exponerlo vía 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: añadir un modal de novedades que se muestra tras una actualización (5827ff8)
- desktop: name the failing token pairing in the re-author hint (cdc130d)
- desktop: analizador para CHANGELOG.md y reglas para mostrar el modal de novedades (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: aceptar encabezados con raya en CHANGELOG.md y eliminar la entrada más reciente codificada de forma fija en la prueba (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)