This guide is for people developing Quoti.
It is separate from user installation instructions. Users should only need to install the built extension. Developers need the tools below to build, test, and evolve the codebase.
- Chrome Extension, Manifest V3.
- React.
- TypeScript.
- Vite.
- CSS with BEM naming.
html-to-imagefor static image export.hls.jsfor video preview playback.
Install these first:
- Git.
- Node.js LTS.
- npm, bundled with Node.js.
- Google Chrome or Chrome for Testing.
- A code editor with TypeScript support.
You do not need Go, Rust, Python, Docker, or system FFmpeg for the current extension build.
Verify the basics:
git --version
node --version
npm --versionInstall dependencies:
npm installRun the Vite development server:
npm run devType-check the project:
npm run typecheckBuild the extension:
npm run buildLoad the extension in Chrome:
- Build with
npm run build. - Open
chrome://extensions. - Enable Developer mode.
- Click
Load unpacked. - Select the
dist/folder. - Open
https://x.com,https://www.threads.com,https://www.linkedin.com, orhttps://www.facebook.com. - Capture a visible post with the Quoti extension.
For UI-only checks, run the dev server and open:
http://localhost:5173/popup.html
Outside the Chrome extension runtime, the popup uses a built-in preview post.
Before changing code:
- Read
AGENT.md. - Read the relevant docs under
docs/. - Keep the change small and reversible.
- Prefer existing patterns over new abstractions.
- Update docs when a behavior, architecture, or workflow changes.
After changing code:
- Run
npm run typecheckfor logic-only changes. - Run
npm run buildfor extension/runtime changes. - Manually reload the unpacked extension in Chrome.
- Test the popup against real supported-platform posts when capture logic changes.
Phase 1 adds FFmpeg WASM inside the extension package.
No system FFmpeg is required for Phase 1.
No Go or Rust is required for Phase 1.
Expected packages when implementation starts:
npm install @ffmpeg/ffmpeg @ffmpeg/util @ffmpeg/coreDo not load FFmpeg WASM code from a CDN in extension runtime. Manifest V3 extensions must package executable code with the extension.
The extension build copies the local FFmpeg core assets into:
dist/assets/ffmpeg/ffmpeg-core.js
dist/assets/ffmpeg/ffmpeg-core.wasm
The Manifest V3 CSP includes wasm-unsafe-eval so Chrome can compile the packaged WebAssembly module. Do not replace the local core URLs with CDN URLs.
The Phase 1 implementation should:
- add a renderer controller contract first;
- keep FFmpeg behind an adapter;
- keep UI components independent from FFmpeg details;
- use local extension-packaged WASM assets;
- preserve
Copy imageas a fallback; - provide progress and useful errors.
Current implementation notes:
- The public entry point is
src/rendering/video/video-render.controller.ts. - The popup loads the video controller lazily and does not call FFmpeg directly.
- When a video post is captured without a playable URL, the popup keeps the post context visible, warms the renderer, and retries media URL hydration before asking the user to refresh.
- Direct MP4 sources are preferred for reliability.
- HLS playlists are downloaded, rewritten, and passed to FFmpeg as local virtual files.
- If WASM rendering fails and a preview video element is available, Quoti falls back to the previous browser WebM renderer.
- The popup keeps media nodes mounted while toggling Text only/With media so loaded image and video context is not lost.
- Tall video media is height-capped in the card layout while preserving the source aspect ratio.
- Video templates render at 1.5x instead of the static image 2x export scale to keep WASM encoding practical.
- The default WASM preset favors faster x264 encoding over smaller output files, then copies audio when MP4-compatible.
Test these cases before considering Phase 1 stable:
- short MP4 video with audio;
- short HLS video with audio;
- vertical video;
- landscape video;
- video without audio;
- video where the preview player is muted;
- post with text plus video;
- post with video only;
- failure to resolve video URL;
- interrupted render.
Phase 2 adds an optional local renderer through Chrome Native Messaging.
The extension must still work without the native renderer installed.
Start with a Node.js and TypeScript helper because the repository already uses TypeScript.
Do not require Go or Rust for the first prototype.
Go or Rust can be considered later if packaging a small single binary becomes more important than sharing TypeScript types.
Install only when working on the native renderer:
- Node.js LTS, already required for the extension.
- A bundled FFmpeg binary at
native/quoti-renderer/vendor/ffmpeg/win32-x64/ffmpeg.exe. - A terminal with permission to write the Native Messaging host manifest during development.
Verify FFmpeg:
npm run native:checkIf the bundled binary is not found on Windows:
- put the production FFmpeg binary at
native/quoti-renderer/vendor/ffmpeg/win32-x64/ffmpeg.exe; - for diagnostics only, run with
QUOTI_FFMPEG_PATHpointing to another FFmpeg executable.
The helper should not silently discover ffmpeg from PATH. Missing bundled FFmpeg must be treated as "native renderer unavailable" so the extension can fall back to WASM.
Chrome starts the native host when the extension calls chrome.runtime.connectNative or chrome.runtime.sendNativeMessage.
The host communicates through stdin/stdout using length-prefixed JSON messages.
Important rules:
- write protocol messages to stdout only;
- write logs to stderr only;
- keep messages small;
- do not send the final video file through Native Messaging;
- return a one-time local download URL or write the file directly from the helper.
The development script should:
- Locate
native/quoti-renderer/bin/quoti-renderer.cmd. - Write a Native Messaging host manifest under the current Windows user profile.
- Register the manifest path under:
HKCU\Software\Google\Chrome\NativeMessagingHosts\com.quoti.renderer
The host manifest should contain:
{
"name": "com.quoti.renderer",
"description": "Quoti local video renderer",
"path": "C:\\Path\\To\\quoti-renderer.cmd",
"type": "stdio",
"allowed_origins": ["chrome-extension://EXTENSION_ID/"]
}During development, the extension ID can change if the extension is loaded from a different folder. Keep this visible in the setup script output.
Register the host with:
npm run native:install -- -ExtensionId <extension-id>Unregister it with:
npm run native:uninstallnative/quoti-renderer/
package.json
src/
host.mjs
scripts/
install-native-host.ps1
uninstall-native-host.ps1
manifests/
com.quoti.renderer.windows.json
vendor/
ffmpeg/
win32-x64/
ffmpeg.exe
The native renderer is intentionally separate from src/ because it runs outside the browser extension.
Phase 3 adds a native Android mobile companion app. iOS remains a future follow-up, but it is not active until a macOS/Xcode and iPhone testing environment is available.
The mobile app is intentionally separate from the browser extension runtime:
mobile/quoti_android/
Shared product data lives in:
contracts/post.schema.json
fixtures/posts/
Do not import TypeScript implementation code into Android. The Kotlin app should implement its own models from the shared contract and use the fixtures for development gallery states and tests.
Additional tools for Phase 3:
- Android Studio or equivalent Android SDK tooling.
- Android emulator or physical Android device for Sharesheet validation.
- macOS, Xcode, and iPhone access later for iOS Share Extension work.
Project agent skills live under .skills/. Quoti-specific workflows are under .skills/quoti, and official Android agent skills are vendored under .skills/android for Android CLI workflows, Compose edge-to-edge behavior, Android testing setup, and experimental Compose Styles API guidance.
Run native Android checks from the app folder:
cd mobile/quoti_android
.\gradlew.bat :app:testDebugUnitTest
.\gradlew.bat :app:assembleDebug
.\gradlew.bat :app:installDebugKeep generated build outputs out of version control and update docs/roadmap/mobile-app-phase-3.md when mobile milestones move.
- Use Chrome DevTools on the extension popup.
- Keep logs concise and remove noisy logs before shipping.
- Prefer user-facing error states over silent failures.
- Test against real X, Threads, LinkedIn, and Facebook timeline posts.
- Watch for virtualized timeline behavior.
- Be careful with hovered post state, visible post state, and context menu state.
- Inspect from
chrome://extensions. - Remember that Manifest V3 service workers can stop and restart.
- Store required transient state explicitly when needed.
- During Native Messaging development, log to stderr.
- Never print debug text to stdout.
- Use Chrome's native messaging error logs when the host does not start.
- Add a standalone CLI test mode before connecting the extension.
Update documentation when:
- a user-visible workflow changes;
- a renderer changes;
- a new permission is added;
- a new dependency is added;
- architecture boundaries change;
- setup requirements change.
Use English for new project documentation.
- Video rendering roadmap:
docs/roadmap/video-rendering-roadmap.md - Mobile app Phase 3 roadmap:
docs/roadmap/mobile-app-phase-3.md - Service architecture:
docs/architecture/service-architecture.md - Frontend architecture:
docs/architecture/frontend-architecture.md - Agent entry point:
AGENT.md