Skip to content

feat: open channel previews from QR deeplinks#2305

Open
riderx wants to merge 45 commits into
mainfrom
codex/preview-channel-deeplink
Open

feat: open channel previews from QR deeplinks#2305
riderx wants to merge 45 commits into
mainfrom
codex/preview-channel-deeplink

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 20, 2026

What

  • QR codes now open Capgo preview deep links that point to a preview payload URL.
  • The scan page accepts channel preview links, bundle preview links, direct preview web URLs, and legacy channel links.
  • New preview payload endpoint returns the bundle/manifest payload from the existing preview file worker.

Why

  • Preview should download and set the exact preview payload, not call the normal updater channel endpoint and hit native-version/channel rollback rules.
  • Bundle and channel preview should follow the same mobile flow.

How

  • Channel QR uses capgo://preview/channel?...&url=<preview.json>.
  • Bundle QR uses capgo://preview/bundle?url=<preview.json>.
  • The app fetches the JSON payload, downloads it, starts preview mode, and sets the bundle directly.
  • The existing preview worker serves /.capgo/preview.json; no update backend or channel-assignment backend code is changed.

Testing

  • bun test tests/preview-links.unit.test.ts
  • bun run typecheck:frontend
  • bun run lint
  • bun test tests/preview-links.unit.test.ts && bun run build
  • bunx cap copy
  • xcodebuild -workspace ios/App/App.xcworkspace -scheme App -configuration Debug -destination generic/platform=iOS CODE_SIGNING_ALLOWED=NO

Not Tested

  • Live physical-device scan against production preview after deployment of the preview payload endpoint.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds channel-preview deep-link contracts, registers native URL schemes and associated domains, installs a native deep-link handler, adds a /preview/channel landing route, extends the scanner to process preview links and start preview sessions, builds channel-specific QR deep links, and threads preview mode through backend queries, validation, and tests.

Changes

Channel preview deep-linking and QR support

Layer / File(s) Summary
Preview deep-link contract and builders
src/services/previewLinks.ts
Defines ChannelPreviewLink and implements buildChannelPreviewDeepLink and parseChannelPreviewDeepLink for /preview/channel URLs.
Native platform URL & associated-domain registration
android/app/src/main/AndroidManifest.xml, ios/App/App/App.entitlements, ios/App/App/Info.plist
Android manifest now lists explicit HTTPS applinks hosts and a separate capgo scheme intent filter; iOS entitlements add com.apple.developer.associated-domains; Info.plist registers capgo and ee.forgr.capacitor_go URL schemes.
Deep-link routing service
src/services/deepLinks.ts
Adds handleDeepLink and installDeepLinkHandler to parse incoming URLs and route channel preview links to /scan?preview=URL or console HTTPS URLs to their original path via Vue Router.
App init, route, and landing page
src/route-map.d.ts, src/main.ts, src/pages/preview/channel.vue, capacitor.config.ts
Adds typed route for /preview/channel, marks it guest-accessible in guestPath, installs the deep-link handler during app init, and provides a landing page that redirects native users to /scan with the current URL in preview query. Includes small Capacitor plugin config refactor used at init.
Channel-specific QR generation and UI wiring
src/components/BundlePreviewFrame.vue, src/pages/app/[app].channel.[channel].preview.vue
Adds optional channelName prop, computes qrCodeUrl using channel preview builder when available, regenerates QR on qrCodeUrl changes, and passes channel.name from the preview page.
Scanner preview processing & preview session
src/pages/scan.vue
On mount reads route.query.preview, parses preview deep links, and when present runs startChannelPreview to fetch, download, and apply the channel preview bundle and start a preview session; retains existing barcode scanning and URL download fallback.
Server preview gating and tests
supabase/functions/_backend/utils/*.ts, tests/updates.test.ts, tests/test-utils.ts
Threads preview flag through Postgres channel selection queries, adds apps.allow_preview column and AppOwnerPostgresResult.allow_preview, validates preview in request bodies, suppresses stats/permission checks in preview mode, and adds tests asserting preview-enabled vs preview-disabled behavior.

Sequence Diagram(s)

sequenceDiagram
  participant OS as Operating System
  participant DeepLinkService as src/services/deepLinks.ts
  participant Router as Vue Router
  participant Scanner as src/pages/scan.vue
  OS->>DeepLinkService: appUrlOpen event or launch URL
  alt Preview deep-link
    DeepLinkService->>Router: navigate /scan?preview=URL
    Router->>Scanner: route with preview param
  else Console HTTPS
    DeepLinkService->>Router: navigate original pathname+search+hash
  end
Loading
sequenceDiagram
  participant Scanner as src/pages/scan.vue
  participant Parser as src/services/previewLinks.ts
  participant Updater as CapacitorUpdater
  participant API as Capgo API
  Scanner->>Scanner: check route.query.preview
  alt Preview param exists
    Scanner->>Parser: parseChannelPreviewDeepLink(url)
    Parser-->>Scanner: ChannelPreviewLink
    Scanner->>API: getLatest({ preview: true }) for channel
    API-->>Scanner: bundle metadata
    Scanner->>Updater: download & set(bundle)
    Updater-->>Scanner: applied
    Scanner->>Updater: startPreviewSession()
  else No preview param
    Scanner->>Scanner: start barcode scanner
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Cap-go/capgo#1993: Overlaps backend channel-selection and preview handling changes to requestInfosPostgres/requestInfosChannelPostgres and related tests.
  • Cap-go/capgo#2031: Touches shared app-shell integration points and may overlap with client deep-link/QR wiring changes.
  • Cap-go/capgo#2301: Related edits to Supabase query helpers in supabase/functions/_backend/utils/pg.ts.

Poem

🐰 I hopped a deep-link through the night,
QR beacons guiding channel light.
Android, iOS, web all sing,
Scan, fetch, apply — previews spring.
A little rabbit cheers this linking flight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature: enabling channel preview opening via QR code deep links.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description covers what was changed (QR codes with preview deep links, scan page enhancements, preview payload endpoint), why (proper preview flow without hitting normal rules), and how (capgo:// URI schemes), with a testing plan and known limitations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@riderx riderx marked this pull request as ready for review May 20, 2026 11:03
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/services/deepLinks.ts (1)

4-4: ⚡ Quick win

Use ~/ alias instead of relative import in src/ code.

Please switch this import to the project alias to match frontend import conventions.

Suggested fix
-import { parseChannelPreviewDeepLink } from './previewLinks'
+import { parseChannelPreviewDeepLink } from '~/services/previewLinks'

As per coding guidelines: "Import using ~/ alias for src/ directory in frontend code instead of relative paths".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/deepLinks.ts` at line 4, The import in deepLinks.ts currently
uses a relative path for parseChannelPreviewDeepLink; update the import to use
the project alias for src by replacing the relative import ("./previewLinks")
with the alias path ("~/services/previewLinks" or whichever alias maps to the
previewLinks module) so parseChannelPreviewDeepLink is imported via the ~/ root
import convention consistent with frontend code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/app/src/main/AndroidManifest.xml`:
- Around line 28-30: The Android app-link intent filter is missing the staging
host declared by isCapgoConsoleHost(); add a new <data android:scheme="https"
android:host="console.staging.capgo.app" /> entry alongside the existing
console.capgo.app, console.dev.capgo.app, and console.preprod.capgo.app <data>
elements inside the app's intent-filter in AndroidManifest.xml so HTTPS deep
links for console.staging.capgo.app will be handed off to the app.

In `@ios/App/App/App.entitlements`:
- Around line 9-12: Add the missing staging associated-domain entry by inserting
the applinks domain string for staging (applinks:console.staging.capgo.app)
alongside the existing applinks entries (e.g., applinks:console.capgo.app,
applinks:console.dev.capgo.app, applinks:console.preprod.capgo.app) in the
App.entitlements so iOS universal links from staging will open the app; ensure
the new string follows the same XML <string>…</string> pattern as the others.

In `@src/pages/scan.vue`:
- Around line 97-103: The code sets the preview channel via
CapacitorUpdater.next() before confirming the preview will work; defer changing
the channel until after startPreviewSession()/getLatest() and any download
succeed, or if you must change it earlier, capture the original channel (via
CapacitorUpdater.current()/current.bundle.id) and ensure you call
CapacitorUpdater.setChannel()/CapacitorUpdater.next() to restore that original
channel in every error path (catch blocks) and before exiting the fallback
branch; update the logic around startPreviewSession(), getLatest(),
CapacitorUpdater.next(), and the error handlers so the preview channel is only
persisted on successful start and is restored on failure.

---

Nitpick comments:
In `@src/services/deepLinks.ts`:
- Line 4: The import in deepLinks.ts currently uses a relative path for
parseChannelPreviewDeepLink; update the import to use the project alias for src
by replacing the relative import ("./previewLinks") with the alias path
("~/services/previewLinks" or whichever alias maps to the previewLinks module)
so parseChannelPreviewDeepLink is imported via the ~/ root import convention
consistent with frontend code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 64b9922e-537d-44de-85e6-07f50db563b1

📥 Commits

Reviewing files that changed from the base of the PR and between 7712cd5 and 4e2111a.

📒 Files selected for processing (11)
  • android/app/src/main/AndroidManifest.xml
  • ios/App/App/App.entitlements
  • ios/App/App/Info.plist
  • src/components/BundlePreviewFrame.vue
  • src/main.ts
  • src/pages/app/[app].channel.[channel].preview.vue
  • src/pages/preview/channel.vue
  • src/pages/scan.vue
  • src/route-map.d.ts
  • src/services/deepLinks.ts
  • src/services/previewLinks.ts

Comment thread android/app/src/main/AndroidManifest.xml
Comment thread ios/App/App/App.entitlements
Comment thread src/pages/scan.vue Outdated
@riderx riderx force-pushed the codex/preview-channel-deeplink branch from 4e2111a to e436e42 Compare May 20, 2026 11:22
@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 20, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/preview-channel-deeplink (7c89bac) with main (0179d50)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/updates.test.ts`:
- Line 587: Change the two new tests from synchronous `it()` to parallel
`it.concurrent()` to follow the repo's parallel-test convention: locate the test
with the description "allows preview requests to fetch a private target channel
when app preview is enabled" and replace `it(` with `it.concurrent(`, and do the
same for the other new test referenced in the comment (around line 646) so both
run concurrently within the same file.
- Around line 618-643: The test flips shared app state by setting allow_preview
to true then later resetting it; wrap the mutable section (the call that sets
allow_preview true, the setup of baseData, the call to postUpdate and assertions
using postUpdate and parseSchema/updateNewScheme) in a try/finally so the final
supabase update that sets allow_preview back to false always runs even if
assertions throw; locate the two supabase.update calls referencing
APP_NAME_UPDATE and move the second one into a finally block, keeping
throwOnError on both updates to surface errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 883969bd-2731-44e8-bd42-df73dc42ebbf

📥 Commits

Reviewing files that changed from the base of the PR and between e436e42 and 381f781.

📒 Files selected for processing (10)
  • capacitor.config.ts
  • src/pages/scan.vue
  • supabase/functions/_backend/utils/pg.ts
  • supabase/functions/_backend/utils/plugin_parser.ts
  • supabase/functions/_backend/utils/plugin_validation.ts
  • supabase/functions/_backend/utils/postgres_schema.ts
  • supabase/functions/_backend/utils/types.ts
  • supabase/functions/_backend/utils/update.ts
  • tests/test-utils.ts
  • tests/updates.test.ts

Comment thread tests/updates.test.ts Outdated
Comment thread tests/updates.test.ts Outdated
Copy link
Copy Markdown

@achimala achimala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch depends on updater APIs that the app does not actually ship yet. src/pages/scan.vue calls getLatest({ appId, channel }) and startPreviewSession({ appId }), but bun.lock still resolves @capgo/capacitor-updater to 8.45.11.

In 8.45.11, GetLatestOptions only contains channel, Android/iOS getLatest only read that channel, and there is no native startPreviewSession plugin method registered. The local type cast masks this from TypeScript, but the QR flow will run against the installed native plugin: the target app id will be ignored, and devices can fall back to the old current()/next()/setShakeMenu() branch instead of starting a real cross-app preview session.

Please land/release the updater-side preview APIs and bump @capgo/capacitor-updater plus bun.lock here before merging this app-side QR flow. At minimum, this should have a version guard or native smoke test so the scanner cannot silently advertise cross-app previews while the bundled native plugin is still on the old API surface.

Copy link
Copy Markdown

@hadessunxy-code hadessunxy-code left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the QR channel preview flow and found one blocker in the native scan path.

Comment thread src/pages/scan.vue Outdated
@thefuturrjfhejh
Copy link
Copy Markdown

I checked the latest scan flow after the updater bump. getLatest uses the preview override now, but startPreviewSession(previewLink.appId) still runs before download() / set().

Risk: if the download or set fails, the catch only shows a toast, while the native preview session has already stored the preview app id and remains active until the user leaves preview. That can leave the container using the target app id even though no preview bundle was applied.

Verification: src/pages/scan.vue calls startPreviewSession before CapacitorUpdater.download() and there is no cleanup call in the catch path. I think downloading first, then starting the preview session immediately before set(bundle), avoids this without changing the API contract.

@riderx riderx force-pushed the codex/preview-channel-deeplink branch from 8d62ea5 to 1d5c20a Compare May 28, 2026 09:49
…-deeplink

# Conflicts:
#	supabase/migrations/20260528090340_manifest_index.sql
@astrobrownmusic
Copy link
Copy Markdown

Reviewed current head 5dfdb29 with focus on the QR preview deep-link contract and the native scan entry path. I also checked that the earlier updater API compatibility concern is no longer present in this branch: package.json/bun.lock now resolve @capgo/capacitor-updater to 8.47.4, and the scan page calls the typed startPreviewSession/getLatest preview APIs instead of relying on the older fallback path for payload-backed links.

Local verification from this branch:

  • bun test tests/preview-links.unit.test.ts -> 4 tests passed

The parser/builders cover payload-backed channel links, legacy web channel links, native capgo:/ variations, and bundle payload links. In scan.vue, payload-backed links go through startPreviewPayload, while the getLatest({ preview: true }) path is kept for legacy channel links without a payload URL. No blocker from this focused pass. I did not do the physical-device production scan listed under Not Tested.

@sonarqubecloud
Copy link
Copy Markdown

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.

6 participants