Skip to content

feat(drive): sync recordings and exports to Google Drive - #1541

Draft
Davidjayan wants to merge 3 commits into
getopenscreen:mainfrom
Davidjayan:feat/google-drive-sync
Draft

Davidjayan wants to merge 3 commits into
getopenscreen:mainfrom
Davidjayan:feat/google-drive-sync

Conversation

@Davidjayan

@Davidjayan Davidjayan commented Nov 12, 2026 •

Copy link
Copy Markdown

Summary

Optional Google Drive sync. Connect your own Google account, and each new recording and each MP4 export is copied to an OpenScreen folder in your Drive. Local files are always kept, and a failed upload never fails the recording or the export.

One Drive file per project, one link. The recording creates the Drive file. Each later MP4 export of the project replaces that file's content in place, so the file id and the link stay the same. A link shared after recording opens the latest edit.

Settings (app menu → Google Drive):

  • OAuth client ID and secret. No client ID ships with OpenScreen, for the same reason the LLM providers' OAuth was removed in 1.8.0. You bring a Desktop app client from your own Google Cloud project, or set OPENSCREEN_GOOGLE_CLIENT_ID / OPENSCREEN_GOOGLE_CLIENT_SECRET.
  • Connect to Google Drive: sign-in in the system browser (loopback redirect + PKCE). The scope is drive.file, so OpenScreen sees only the files it created.
  • Sync to Google Drive switch: syncing can be turned off without disconnecting.
  • Disconnect revokes the token. Files already in Drive stay there, with their links.

Load Project list:

  • A Drive icon on projects saved in Drive. It shows a spinner while uploading and a warning if the last upload failed.
  • A ⋮ menu on those rows:
    • Share drive link: copies the link. The toast says when only you can open it.
    • Edit drive visibility: Only you / Anyone with the link.
    • Unsync from drive: stops syncing that project, so later exports stay local. The Drive copy and its link are kept; unsync is not a delete.

Code:

  • electron/drive/:
    • google-oauth.ts: sign-in, refresh, revoke.
    • google-drive-api.ts: folder, resumable upload sent as one streamed PUT (takes run to GB), permissions. Plain fetch, no googleapis dependency.
    • google-drive-store.ts: settings JSON, plus safeStorage for the refresh token and client secret. The keychain is read lazily, never at startup.
    • google-drive-service.ts: orchestration. Uploads for one project are queued, so an export finishing mid-upload lands on top of it instead of becoming a second file. Two edge cases:
      • If the file was deleted in Drive, the service uploads a new one.
      • If Google refuses the refresh token (invalid_grant), the account is disconnected.
  • A new drive bridge domain. Payloads are validated in nativeBridge.ts.
  • src/lib/driveSync.ts is the renderer's single, never-throwing call. It is used by importPendingRecording and by ExportDialog (MP4 only; a GIF would change what a shared link shows).
  • GoogleDriveSettings.tsx (new EditorDialogSection "drive") and ProjectDriveMenu.tsx. The row menu opens in a portal, so the scrolling project list cannot clip it.
  • The browser shim keeps a fake Drive connection in localStorage, so this UI can be driven in browser mode.
  • Strings in all 15 locales.
  • Docs:
    • technical-documentation/architecture/google-drive-sync.md (+ README row).
    • A Google Drive sync section in the manual e2e checklist.
    • The FAQ's "no cloud storage" bullet updated in English and all 7 site languages.

Note for maintainers: the FAQ listed "no share links, cloud storage" under what OpenScreen doesn't do. This keeps OpenScreen hosting nothing. Drive is the user's own account, off by default, with the user's own OAuth client. Happy to adjust the scope if you'd rather draw the line elsewhere.

Related issue

No issue filed.

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Screenshots / video

Captured from the renderer in browser mode (NO_ELECTRON=1 npm run dev, ?windowType=editor), with the Drive shim seeded with a connected account. These show the UI only; no real Google sign-in or upload is in them.

App menu Load Project: Drive icons (synced, uploading)
App menu with the Google Drive row Project list with Drive badges

Row menu: Share drive link, Edit drive visibility, Unsync from drive

Drive row menu with the visibility choices open

Settings: first setup → connect → connected, with the sync switch

Settings asking for the OAuth client
Settings offering Connect to Google Drive
Settings connected, Sync to Google Drive on

Testing

Automated:

  • npx tsc --noEmit and npx tsc -p tsconfig.test.json --noEmit pass.
  • npm run lint: no new diagnostics (36 warnings, the same as main).
  • npm run i18n:check passes.

New tests:

  • google-oauth.test.ts: PKCE, the consent URL and scope, and a real loopback sign-in round trip. Also a forged state is refused, refresh, invalid_grant, and the timeout.
  • google-drive-api.test.ts: create vs replace-in-place, Drive errors, permissions, the folder. Also a streamed upload through Node's real fetch into a local server, which checks every byte arrives.
  • google-drive-service.test.ts: against an in-memory fake Drive. Covers env-over-settings client, sync on/off, the same file id across recording and exports, overlapping uploads queued, a Drive-deleted file re-created, a revoked token disconnecting, failed-upload state, visibility, and unsync.
  • google-drive-store.test.ts: persistence, credentials kept out of the JSON, refusal without the keychain, defensive parsing.
  • nativeBridge.drive.test.ts: payload validation, and UNAVAILABLE without a Drive service.
  • driveSync.test.ts.
  • GoogleDriveSettings.test.tsx.
  • OpenProjectModal.drive.test.tsx: badge, the three menu items, copy, visibility, unsync, disabled items while uploading, and the menu not clipped by the list. Escape closes the menu, not the dialog.
  • EditorTopBar.test.tsx: the new menu row.

npm run test: all pass (394 files, 5470 tests, 1 skipped).

  • One commit here fixes an existing intermittent failure in LeftPanel.copyMessage.test.tsx. Its first waitFor allowed 1 s for the panel's first render.
  • Under load that test also failed on clean main (running src/components/ai-edition alone: 1 failed / 89 passed), while the file alone passed.
  • It now allows 10 s, for the same reason as testTimeout in vitest.config.ts.

Manual:

  • The UI in the screenshots was driven in browser mode with Playwright: open the dialog, enter a client, connect state, toggle, the row menu, and a visibility change.
  • Not run: a real Google sign-in and a real upload from the Electron app. They need an OAuth client and a Google account, and computer-use was not available in this session. The new Google Drive sync section of manual-e2e-checklist.md lists those checks; they are still to do.

🤖 Generated with Claude Code

Davidjayan and others added 2 commits November 12, 2026 09:20
Connect a Google account from the new Google Drive dialog (app menu). While
sync is on, each new recording is uploaded to an OpenScreen folder in the
user's Drive, and each MP4 export of a project replaces that project's
Drive file in place, so its link always opens the latest edit. Local files
are never touched, and a failed upload never fails the recording or export.

- Settings: OAuth client (or OPENSCREEN_GOOGLE_CLIENT_ID/_SECRET), connect,
  a Sync to Google Drive switch, disconnect. No client ID ships.
- Load Project: a Drive icon on synced rows, and a row menu with Share
  drive link, Edit drive visibility and Unsync from drive.
- Main process: loopback OAuth with PKCE and the drive.file scope,
  resumable streamed uploads, credentials in safeStorage.
- Strings in all 15 locales, an architecture doc, manual checklist items,
  and the FAQ no longer says there is no cloud storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Load Project list scrolls, and the Drive menu inside it showed only its
first rows. It now opens in a portal with fixed positioning, above or below
its button, and closes when the list scrolls.

The browser shim keeps a Drive connection and project states in
localStorage, so the dialog and the menu can be driven in browser mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

The first test's waitFor used the 1 s default for the panel's first render,
which a loaded machine misses: the full suite failed here on main too, while
the file alone passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant