Add export environment plan
This commit is contained in:
1 parent
f469ab7444
commit
b18e66c28d
4 files changed
+295
-2
No files matched your search
@@ -0,0 +1,293 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user