9.5 KiB
Export Environment Copy Plan
Date: 2026-06-07
Goal
Make the export dialog copy describe what actually happens in each runtime:
- Browser/PWA: MP4 uses the render server; WebM records through browser
MediaRecorder. - Electron desktop: MP4 uses the desktop render bridge and bundled ffmpeg;
WebM still records through the Chromium
MediaRecorderruntime inside Electron.
The current labels are accurate for the browser build, but misleading inside Electron:
SERVER - FRAME-ACCURATEon MP4, even when Electron is rendering locally.BROWSER - REALTIMEon WebM, even when the app is running as Electron.
Current Code
Primary files:
src/components/ExportDialog.vuesrc/i18n/messages.tssrc/lib/desktopRenderExport.tssrc/types/desktop.d.ts
Runtime detection already exists as an exported helper in
desktopRenderExport.ts:
export function hasDesktopRender(): boolean {
return !!window.ruwioDesktop?.renderMp4
}
This is the right signal for MP4 copy because ExportDialog.vue routes MP4
through renderMp4OnDesktop() when hasDesktopRender() returns true, otherwise
it calls downloadFromServer(). The MP4 copy should reuse this exact function
rather than reimplementing the predicate, so the text can never drift from the
route (see Implementation Plan step 1).
window.ruwioDesktop is injected by the Electron preload before any app module
loads and never changes during a session, so a runtime check is stable at
module-init time — no reactivity is required to read it correctly.
Proposed Copy Matrix
MP4
Browser/PWA:
- Badge:
Server · Frame-by-frame - Description:
H.264 export via render server - Progress:
Rendering MP4 with render server...
Electron:
- Badge:
Desktop · Frame-by-frame - Description:
H.264 export via bundled ffmpeg - Progress:
Rendering MP4 in desktop app...
Rationale: both MP4 paths render deterministic frames through server/render.html.
The key difference is where rendering happens: HTTP render server vs Electron
desktop worker plus ffmpeg.
Avoid Frame-accurate in the short badge. It sounds like a stronger quality
claim than the dialog needs, and it obscures the real user-facing distinction:
server vs desktop. The help text can explain frame-by-frame rendering if needed.
Badge style: store badges in title case with a · middot separator, matching
the existing sibling badges (gifBadge: 'Browser · Frame-by-frame',
apngBadge: 'Browser · Lossless'). The .badge CSS already applies
text-transform: uppercase, so do not write the strings in all-caps or
switch to a - hyphen separator — that would render MP4/WebM inconsistently
next to GIF/APNG in the same column.
WebM
Browser/PWA:
- Badge:
Browser · Realtime - Description:
Recorded in real time with MediaRecorder - Progress:
Rendering WebM in browser realtime...
Electron:
- Badge:
Chromium · Realtime - Description:
Recorded in real time by Electron's Chromium runtime - Progress:
Rendering WebM in Chromium realtime...
Rationale: the implementation does not become a desktop ffmpeg pipeline in
Electron. It still uses MediaRecorder from the renderer process. Calling that
browser in the desktop app is imprecise, but Chromium is technically true
and short enough for the badge.
Implementation Plan
-
Add environment booleans in
ExportDialog.vue.import { hasDesktopRender } from '@/lib/desktopRenderExport' const isDesktop = computed(() => !!window.ruwioDesktop) const hasDesktopMp4 = computed(() => hasDesktopRender())Use
hasDesktopMp4for MP4 text. It wrapshasDesktopRender()— the same function the MP4 route already calls inexportMp4Server()— so the copy decision and the actual route share one predicate and can never diverge. Do not reimplement!!window.ruwioDesktop?.renderMp4inline.Use
isDesktopfor WebM text because WebM is still available through renderer Chromium even if MP4 desktop rendering is unavailable; there is no existing helper for that distinction, so a local boolean is fine. -
Rename
exportMp4Server()toexportMp4().The current name is now misleading because the function already branches:
- Desktop:
renderMp4OnDesktop(scene, ...) - Browser/server:
downloadFromServer(scene, ...)
- Desktop:
-
Replace static MP4 bindings.
Current:
{{ t('export.mp4Badge') }} {{ t('export.mp4Desc') }}Proposed:
{{ t(hasDesktopMp4 ? 'export.mp4DesktopBadge' : 'export.mp4ServerBadge') }} {{ t(hasDesktopMp4 ? 'export.mp4DesktopDesc' : 'export.mp4ServerDesc') }} -
Replace static WebM bindings.
Current:
{{ t('export.webmBadge') }} {{ videoSupported ? t('export.webmDesc') : t('export.webmNotSupported') }}Proposed:
{{ t(isDesktop ? 'export.webmDesktopBadge' : 'export.webmBrowserBadge') }} {{ videoSupported ? t(isDesktop ? 'export.webmDesktopDesc' : 'export.webmBrowserDesc') : t('export.webmNotSupported') }} -
Split progress labels.
MP4 currently uses:
label: t('export.labelMp4')Proposed:
label: t(hasDesktopMp4.value ? 'export.labelMp4Desktop' : 'export.labelMp4Server')WebM currently uses:
label: t('export.labelWebm')Proposed:
label: t(isDesktop.value ? 'export.labelWebmDesktop' : 'export.labelWebmBrowser') -
Update
src/i18n/messages.tsin both locales.English keys to add:
mp4ServerBadge: 'Server · Frame-by-frame', mp4ServerDesc: 'H.264 export via render server', mp4DesktopBadge: 'Desktop · Frame-by-frame', mp4DesktopDesc: 'H.264 export via bundled ffmpeg', webmBrowserBadge: 'Browser · Realtime', webmBrowserDesc: 'Recorded in real time with MediaRecorder', webmDesktopBadge: 'Chromium · Realtime', webmDesktopDesc: "Recorded in real time by Electron's Chromium runtime", labelMp4Server: 'Rendering MP4 with render server...', labelMp4Desktop: 'Rendering MP4 in desktop app...', labelWebmBrowser: 'Rendering WebM in browser realtime...', labelWebmDesktop: 'Rendering WebM in Chromium realtime...',Badges stay title case with a
·separator; the.badgeCSS uppercases them at render time (see the MP4 copy-matrix note above). German keys should mirror the meaning rather than preserve the old wording. -
Fix the export-button help string
exportHelpinmessages.ts.This is separate from the help dialog and is easy to miss. Both locales currently hardcode the misleading claim:
messages.ts:43(en): "...MP4 needs the render server; WebM records in real time in the browser."messages.ts:1153(de): "...MP4 braucht den Render-Server. WebM wird direkt im Browser in Echtzeit aufgenommen."
Add runtime-branched variants (e.g.
exportHelpServer/exportHelpDesktop) and select with the sameisDesktopsignal wherevert('...exportHelp')is consumed. This is exactly the "globally visible text saying MP4 always needs the render server" that must not be left in place. -
Update help-dialog format text in
messages.ts, and branch it now.The strings live at
help.content.export.formats.mp4/.webm(messages.ts:1081/1083en,:1923/1925de) andexportHelp(handled in step 7). Browser help should describe:- MP4 as render-server frame-by-frame export.
- WebM as browser
MediaRecorderrealtime recording.
Electron help should describe:
- MP4 as bundled-ffmpeg desktop frame-by-frame export.
- WebM as Chromium
MediaRecorderrealtime recording.
Wiring the branch is cheaper than the old draft assumed.
HelpDialog.vuedefinesCHAPTERS: Chapter[]as a module-levelconstwith staticbodyKeystrings, butwindow.ruwioDesktopis set at preload before that module loads and never changes, so a plain ternary on thebodyKeyresolves correctly at module-init time without makingCHAPTERSreactive:{ bodyKey: window.ruwioDesktop?.renderMp4 ? 'help.content.export.formats.mp4Desktop' : 'help.content.export.formats.mp4' }, { bodyKey: window.ruwioDesktop ? 'help.content.export.formats.webmDesktop' : 'help.content.export.formats.webm' },Add the
*Desktopkeys in both locales and wire the branch in the same change. Do not defer this and leave the help dialog asserting MP4 always needs the render server.
Verification
Browser/Vite:
- Open the export dialog.
- MP4 badge renders
SERVER · FRAME-BY-FRAME(stored title case, uppercased by CSS). - MP4 description mentions the render server.
- WebM badge renders
BROWSER · REALTIME. - WebM description mentions
MediaRecorder. - Export-button help (
exportHelp) and help dialog describe MP4 as a render-server export.
Electron:
- Open the export dialog.
- MP4 badge renders
DESKTOP · FRAME-BY-FRAME. - MP4 description mentions bundled ffmpeg.
- WebM badge renders
CHROMIUM · REALTIME. - WebM description mentions Electron Chromium.
- Export-button help (
exportHelp) and help dialog describe MP4 as a bundled-ffmpeg desktop export, not the render server.
Build checks:
npm run build
npm run desktop:dir
Optional automated coverage:
- Add a small in-app devtools test that stubs/removes
window.ruwioDesktopand verifies the selected i18n keys or rendered badge text.src/devtools/headless.tsalready reads!!window.ruwioDesktop(line 19) and is the natural home for it. - Add an Electron harness assertion that export dialog MP4 copy is desktop copy.