294 lines
9.5 KiB
Markdown
294 lines
9.5 KiB
Markdown
# 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 `MediaRecorder` runtime inside
|
|
Electron.
|
|
|
|
The current labels are accurate for the browser build, but misleading inside
|
|
Electron:
|
|
|
|
- `SERVER - FRAME-ACCURATE` on MP4, even when Electron is rendering locally.
|
|
- `BROWSER - REALTIME` on WebM, even when the app is running as Electron.
|
|
|
|
## Current Code
|
|
|
|
Primary files:
|
|
|
|
- `src/components/ExportDialog.vue`
|
|
- `src/i18n/messages.ts`
|
|
- `src/lib/desktopRenderExport.ts`
|
|
- `src/types/desktop.d.ts`
|
|
|
|
Runtime detection already exists as an exported helper in
|
|
`desktopRenderExport.ts`:
|
|
|
|
```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
|
|
|
|
1. Add environment booleans in `ExportDialog.vue`.
|
|
|
|
```ts
|
|
import { hasDesktopRender } from '@/lib/desktopRenderExport'
|
|
|
|
const isDesktop = computed(() => !!window.ruwioDesktop)
|
|
const hasDesktopMp4 = computed(() => hasDesktopRender())
|
|
```
|
|
|
|
Use `hasDesktopMp4` for MP4 text. It wraps `hasDesktopRender()` — the *same*
|
|
function the MP4 route already calls in `exportMp4Server()` — so the copy
|
|
decision and the actual route share one predicate and can never diverge. Do
|
|
not reimplement `!!window.ruwioDesktop?.renderMp4` inline.
|
|
|
|
Use `isDesktop` for 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.
|
|
|
|
2. Rename `exportMp4Server()` to `exportMp4()`.
|
|
|
|
The current name is now misleading because the function already branches:
|
|
|
|
- Desktop: `renderMp4OnDesktop(scene, ...)`
|
|
- Browser/server: `downloadFromServer(scene, ...)`
|
|
|
|
3. Replace static MP4 bindings.
|
|
|
|
Current:
|
|
|
|
```vue
|
|
{{ t('export.mp4Badge') }}
|
|
{{ t('export.mp4Desc') }}
|
|
```
|
|
|
|
Proposed:
|
|
|
|
```vue
|
|
{{ t(hasDesktopMp4 ? 'export.mp4DesktopBadge' : 'export.mp4ServerBadge') }}
|
|
{{ t(hasDesktopMp4 ? 'export.mp4DesktopDesc' : 'export.mp4ServerDesc') }}
|
|
```
|
|
|
|
4. Replace static WebM bindings.
|
|
|
|
Current:
|
|
|
|
```vue
|
|
{{ t('export.webmBadge') }}
|
|
{{ videoSupported ? t('export.webmDesc') : t('export.webmNotSupported') }}
|
|
```
|
|
|
|
Proposed:
|
|
|
|
```vue
|
|
{{ t(isDesktop ? 'export.webmDesktopBadge' : 'export.webmBrowserBadge') }}
|
|
{{
|
|
videoSupported
|
|
? t(isDesktop ? 'export.webmDesktopDesc' : 'export.webmBrowserDesc')
|
|
: t('export.webmNotSupported')
|
|
}}
|
|
```
|
|
|
|
5. Split progress labels.
|
|
|
|
MP4 currently uses:
|
|
|
|
```ts
|
|
label: t('export.labelMp4')
|
|
```
|
|
|
|
Proposed:
|
|
|
|
```ts
|
|
label: t(hasDesktopMp4.value ? 'export.labelMp4Desktop' : 'export.labelMp4Server')
|
|
```
|
|
|
|
WebM currently uses:
|
|
|
|
```ts
|
|
label: t('export.labelWebm')
|
|
```
|
|
|
|
Proposed:
|
|
|
|
```ts
|
|
label: t(isDesktop.value ? 'export.labelWebmDesktop' : 'export.labelWebmBrowser')
|
|
```
|
|
|
|
6. Update `src/i18n/messages.ts` in both locales.
|
|
|
|
English keys to add:
|
|
|
|
```ts
|
|
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 `.badge` CSS uppercases
|
|
them at render time (see the MP4 copy-matrix note above). German keys should
|
|
mirror the meaning rather than preserve the old wording.
|
|
|
|
7. Fix the export-button help string `exportHelp` in `messages.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 same `isDesktop` signal wherever `t('...exportHelp')` is
|
|
consumed. This is exactly the "globally visible text saying MP4 always needs
|
|
the render server" that must not be left in place.
|
|
|
|
8. 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/1083` en, `:1923/1925` de) and `exportHelp` (handled in
|
|
step 7). Browser help should describe:
|
|
|
|
- MP4 as render-server frame-by-frame export.
|
|
- WebM as browser `MediaRecorder` realtime recording.
|
|
|
|
Electron help should describe:
|
|
|
|
- MP4 as bundled-ffmpeg desktop frame-by-frame export.
|
|
- WebM as Chromium `MediaRecorder` realtime recording.
|
|
|
|
Wiring the branch is cheaper than the old draft assumed. `HelpDialog.vue`
|
|
defines `CHAPTERS: Chapter[]` as a module-level `const` with static
|
|
`bodyKey` strings, but `window.ruwioDesktop` is set at preload before that
|
|
module loads and never changes, so a plain ternary on the `bodyKey` resolves
|
|
correctly at module-init time without making `CHAPTERS` reactive:
|
|
|
|
```ts
|
|
{ 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 `*Desktop` keys 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:
|
|
|
|
```bash
|
|
npm run build
|
|
npm run desktop:dir
|
|
```
|
|
|
|
Optional automated coverage:
|
|
|
|
- Add a small in-app devtools test that stubs/removes `window.ruwioDesktop` and
|
|
verifies the selected i18n keys or rendered badge text. `src/devtools/headless.ts`
|
|
already 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.
|