feat(frontend): native-incompatibility guidance + Builder CTA on the Compatibility page#2480
Conversation
…ies page When the dependency diff is incompatible, show an actionable guidance panel (modeled on the incompatible-bundle email): what it means (OTA can't change native code, devices on the old native build may crash), how to fix it (rebuild with Capgo Builder — opens the sell deck — or ship a native build; roll the channel back to the last compatible bundle meanwhile), and a collapsible 'why native changes need an app-store update' explainer. Tracks builder_cta_compatibility_clicked.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a compatibility fix guidance card for unresolved events, including rebuild and rollback actions, and updates backend compatibility-event processing to suppress duplicate mirror events when channels revert to an unresolved baseline. ChangesCompatibility remediation flow
Sequence Diagram(s)sequenceDiagram
participant User
participant CompatibilityPage
participant PostHog
participant Supabase
participant Router
User->>CompatibilityPage: Open unresolved compatibility view
CompatibilityPage->>User: Render fix guidance card
User->>CompatibilityPage: Click rebuild CTA
CompatibilityPage->>PostHog: Track click with app_id
CompatibilityPage->>Router: Navigate to /builds
User->>CompatibilityPage: Confirm rollback
CompatibilityPage->>Supabase: Update channel bundle targets
CompatibilityPage->>CompatibilityPage: Refresh compatibility data
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
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 `@src/pages/app/`[app].bundle.[bundle].dependencies.vue:
- Around line 447-453: The button in the template that calls openBuilder uses
custom Tailwind classes; replace those with DaisyUI button classes to ensure
consistent interactive styling—swap the class list on the <button> that
references openBuilder to use the DaisyUI primary/ghost classes (d-btn and
appropriate modifier like d-btn-primary or d-btn-sm/d-btn-lg as needed) and
remove the bespoke bg-blue-500/hover/bg/ focus ring utilities; keep the same
attributes (type="button" and `@click`="openBuilder") and the existing translation
text (t('compat-fix-rebuild-cta')) so behavior and content remain unchanged.
- Line 452: Replace the hardcoded "→" after the translation key with the shared
arrow icon component (e.g., use IconChevronRight or IconArrowRight) to match
project UI patterns; update the component registration/import where this
template lives and swap the literal arrow in the template that contains "{{
t('compat-fix-rebuild-cta') }} →" for the icon component (apply any existing
sizing/spacing classes used for other icons to maintain visual consistency).
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e9ef9ba5-7489-4d1e-9ed2-5a9d1e7a2182
📒 Files selected for processing (2)
messages/en.jsonsrc/pages/app/[app].bundle.[bundle].dependencies.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
…tibility page The per-bundle dependency-compare view was the wrong home for the 'this needs a native build' explainer. Move it to the app-level Compatibility page (/app/:app/compatibility), shown whenever the app has unresolved incompatibility events. Make the copy generic (no per-bundle name), keep the Capgo Builder CTA (sell deck) + rollback guidance + the collapsible 'why native changes need an app-store update' explainer. Revert the Dependencies page to its original state.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/pages/app/`[app].compatibility.vue:
- Around line 393-410: Replace the hand-crafted button class strings with the
project's DaisyUI primitives: for the rebuild CTA button referenced by
`@click`="openBuilder" and data-test="compatibility-rebuild-cta" remove the custom
inline classes and use the d- prefixed button variant (e.g., d-btn and the
appropriate modifier such as d-btn-primary) so spacing/behavior matches the rest
of the app; likewise replace the channel-manage inline-styled button that calls
router.push(`/app/${encodeURIComponent(id)}/channels`) with the DaisyUI
button/link primitive (e.g., d-btn or d-btn-ghost) and keep the same
`@click/router.push` logic and text, preserving accessibility attributes and
data-test where present.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 972b608d-9577-49dc-9c1a-01e0736eda47
📒 Files selected for processing (2)
messages/en.jsonsrc/pages/app/[app].compatibility.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
- Cleaner, more readable layout: card header + two distinct fix-option cards (rebuild / roll back) instead of dense stacked paragraphs; bump body copy to text-sm. - The Builder CTA now opens the in-app Builds tab instead of the 5-slide presentation deck. - Add a docs link to the bundle-compatibility / disable-updates strategy section.
…sible - Drop the amber header band / border / icon circle for the standard neutral card palette (slate borders, white/slate-900 bg); keep just a small amber alert icon as the cue. - Make the whole guidance panel collapsible via a header toggle, persisting the collapsed/expanded choice in localStorage (default expanded).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/pages/app/`[app].compatibility.vue:
- Around line 68-74: The openBuilder function currently routes to the Builds tab
using router.push(`/app/${encodeURIComponent(id.value)}/builds`) but the PR
intends to open the 5-slide BuilderPresentationModal; update openBuilder to
invoke the modal presentation instead of navigating (or, if Builds is correct,
update the PR text). Specifically, replace the router.push call in openBuilder
with the same modal-opening logic used by other CTAs (e.g., the method or event
that shows BuilderPresentationModal in other components) and keep the telemetry
pushEvent('builder_cta_compatibility_clicked', config.supaHost, { app_id:
id.value }) intact; if you choose to keep routing, change the PR description to
state that the CTA navigates to Builds rather than opening the modal.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2f62beff-bea0-4ed7-9802-29dfd7cdc5b6
📒 Files selected for processing (2)
messages/en.jsonsrc/pages/app/[app].compatibility.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
…ty docs link - 'Roll back the channel' card now has a primary 'Roll back for me' button that, after a confirm dialog listing the exact changes (channel → last compatible bundle), points each affected channel back to the previous bundle from its most recent unresolved compatibility event. Skips deleted channels/bundles; checks channel.promote_bundle per channel. 'Manage channels' stays as the secondary button. - Point the docs link at the live-updates compatibility guide.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/app/[app].compatibility.vue (1)
166-175: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueOptional: localStorage key name and stored values are inverted.
The key is
capgo-compat-guidance-collapsedbut stores'0'when open and'1'when collapsed (line 174). The logic is correct but readinglocalStorage.getItem(guidanceCollapseKey) !== '1'(line 169) to determineguidanceOpenis less intuitive than storing the open state directly or renaming the key to match the stored semantics.♻️ Option: store the open state
-const guidanceCollapseKey = 'capgo-compat-guidance-collapsed' -const guidanceOpen = ref(typeof localStorage === 'undefined' || localStorage.getItem(guidanceCollapseKey) !== '1') +const guidanceOpenKey = 'capgo-compat-guidance-open' +const guidanceOpen = ref(typeof localStorage === 'undefined' || localStorage.getItem(guidanceOpenKey) === '1') function toggleGuidance() { guidanceOpen.value = !guidanceOpen.value if (typeof localStorage !== 'undefined') - localStorage.setItem(guidanceCollapseKey, guidanceOpen.value ? '0' : '1') + localStorage.setItem(guidanceOpenKey, guidanceOpen.value ? '1' : '0') }🤖 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/pages/app/`[app].compatibility.vue around lines 166 - 175, The code uses guidanceCollapseKey and stores '0' for open and '1' for collapsed which is counterintuitive; change the storage to represent the open state directly (or rename the key to guidance-open). Update guidanceOpen's initialization to read localStorage.getItem(guidanceOpenKey) === '1' (or !== '0' if you keep booleans inverted) and update toggleGuidance to set localStorage.setItem(guidanceOpenKey, guidanceOpen.value ? '1' : '0') so the stored value matches the meaning of guidanceOpen; adjust symbol names (guidanceCollapseKey → guidanceOpenKey or similar) and ensure toggleGuidance and guidanceOpen use the new key and consistent stored values.
🤖 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 `@src/pages/app/`[app].compatibility.vue:
- Around line 109-139: The rollbackChannels function currently leaves partial
state and reports only a generic error; change it to first check permissions for
all rollbackTargets (use checkPermissions in the initial loop) and collect any
permission-denied targets and abort with a clear toast listing denied channel
names (do not proceed if any denied), then perform the updates but collect
per-channel results (successes and failures) when calling
supabase.from('channels').update for each target, log detailed errors with
channelName/channelId, and finally show a detailed toast summarizing "rolled
back: {succeeded}; failed: {failed}" (add the i18n key like
rollback-partial-failure) instead of a single generic error, and still call
refreshData() after reporting.
- Around line 113-119: The permission check loop using
checkPermissions('channel.promote_bundle', { channelId: target.channelId })
returns a generic toast via toast.error(t('no-permission')) which doesn't
indicate which target.channelId failed; update the failure path in the loop to
call toast.error with a channel-specific message key (e.g.,
t('no-permission-channel', { channel: target.name || target.channelId })) so the
user sees which channel blocked the action, and add the new
"no-permission-channel" entry to messages/en.json with a template like "You do
not have permission to update channel \"{channel}\"".
---
Outside diff comments:
In `@src/pages/app/`[app].compatibility.vue:
- Around line 166-175: The code uses guidanceCollapseKey and stores '0' for open
and '1' for collapsed which is counterintuitive; change the storage to represent
the open state directly (or rename the key to guidance-open). Update
guidanceOpen's initialization to read localStorage.getItem(guidanceOpenKey) ===
'1' (or !== '0' if you keep booleans inverted) and update toggleGuidance to set
localStorage.setItem(guidanceOpenKey, guidanceOpen.value ? '1' : '0') so the
stored value matches the meaning of guidanceOpen; adjust symbol names
(guidanceCollapseKey → guidanceOpenKey or similar) and ensure toggleGuidance and
guidanceOpen use the new key and consistent stored values.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 595def3a-9ccf-42df-9be3-5284e9551189
📒 Files selected for processing (2)
messages/en.jsonsrc/pages/app/[app].compatibility.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
Backend: rolling a channel back to the baseline of an unresolved compatibility event used to raise a fresh mirror event (prev/cur swapped) while only the original auto-resolved — so the recommended remediation created an endless chain of unresolved events. decideCompatibilityEvents now takes the channel's unresolved events and suppresses an event whose current bundle is a known unresolved baseline for that channel+platform (a revert); the original event still auto-resolves on the same pass. Suppression is channel-scoped and only applies while the event is unresolved (an accepted incompatibility later reverted still raises). Frontend: rollback now shows a single toast and re-refreshes at 4s/10s so the queued auto-resolution becomes visible without a manual reload. 3 new unit tests for the revert suppression.
There was a problem hiding this comment.
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 `@supabase/functions/_backend/triggers/compatibility_events.ts`:
- Around line 159-170: The revert suppression predicate
(isRevertToKnownBaseline) is too broad and can match stale unresolved events;
update the unresolvedEvents.some(...) check to also require that the unresolved
event's current_version_id equals the bundle id we're reverting from
(previous.bundle.id) so only true mirror rollbacks are suppressed—i.e., keep the
existing checks against newChannel.id, previous.platform and
previous_version_id/currentBundle.id and add a condition verifying
event.current_version_id === previous.bundle.id.
In `@tests/compatibility-events-decide.unit.test.ts`:
- Around line 215-285: Update the three newly added test cases that call it(...)
to use it.concurrent(...) instead: "suppresses the mirror event when reverting
to an unresolved event's baseline", "does NOT suppress when the unresolved
baseline belongs to another channel", and "does NOT suppress an incompatible
change to a bundle no unresolved event knows as a baseline". Leave test bodies
and fixtures (decideCompatibilityEvents, newChannel, bundle, CHANGE_AT,
PKG_V6/PKG_V7) unchanged—only replace the it(...) invocations with
it.concurrent(...) so they run in parallel.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: ce0c5c21-dcff-4848-96c2-35d9b3b2cf64
📒 Files selected for processing (5)
messages/en.jsonsrc/pages/app/[app].compatibility.vuesupabase/functions/_backend/triggers/compatibility_events.tssupabase/functions/_backend/triggers/on_channel_update.tstests/compatibility-events-decide.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
…arded rollback - Revert the on_channel_update / compatibility_events trigger changes from this branch; the mirror-event suppression now lives in the backend-only PR #2482 (gated on disable_auto_update_under_native) so it can merge and deploy first. - Rollback confirm dialog now appends a warning when an affected channel has the downgrade guard off: devices already on a newer native build could receive the older bundle — suggest enabling the guard first.
…ompatible-fix-guidance
- Track the 4s/10s post-rollback refresh timers and clear them on unmount (and on a subsequent rollback) instead of firing into a dead component. - Per-channel rollback outcome: partial failures now name the failed channel(s) and acknowledge which channels did roll back, instead of one generic error implying nothing happened. - Snapshot rollback targets at dialog-open and pass them to the handler, so the writes performed on confirm are exactly the changes the dialog displayed. - Soften the rollback card copy: 'the bundle it served before the incompatible change' instead of overpromising 'last compatible bundle / users stay safe' for chained-incompatibility cases; same for the rollbackTargets comment.
- Pre-check channel.promote_bundle per rollback target (request-id guarded watch) and gate the 'Roll back for me' CTA + confirm dialog on the permitted subset, so users without permission are never offered an action that would fail. - A late permission failure (revoked between dialog open and confirm) now skips just that channel and names it in the outcome toast instead of aborting the whole batch with an anonymous no-permission error.
Address review: interactive elements use DaisyUI primitives per AGENTS.md; replace the custom Tailwind button styling on the rebuild, rollback and manage-channels CTAs with d-btn variants.
|



What
The app Compatibility page (
/app/:app/compatibility) now explains what an incompatibility means and how to fix it, modeled on the (currently disabled) incompatible-bundle notification email. Companion to the merged backend fix #2482.When the app has any unresolved compatibility event, a collapsible guidance panel (neutral card, localStorage-persisted collapse state) appears above the events table:
/app/:app/builds); firesbuilder_cta_compatibility_clicked.Rollback safety
channel.promote_bundleon (the permission the DB trigger enforces on everychannels.versionwrite); users without permission are never offered the action.disable_auto_update_under_nativeOFF get an explicit warning in the confirm dialog (devices on a newer native build could receive the older bundle).Test plan
oxlint,eslint,vue-tscall green.