Skip to content

feat(updates): persist a Stable / Pre-Release channel selector in Settings > Updates#6359

Open
gatsby74 wants to merge 3 commits into
stablyai:mainfrom
gatsby74:gatsby74/feat-updates-release-channel
Open

feat(updates): persist a Stable / Pre-Release channel selector in Settings > Updates#6359
gatsby74 wants to merge 3 commits into
stablyai:mainfrom
gatsby74:gatsby74/feat-updates-release-channel

Conversation

@gatsby74

@gatsby74 gatsby74 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Release Channel selector (Stable / Pre-Release) under Settings > Updates so users can opt into release-candidate builds without the hidden Shift-click menu shortcut. The choice persists through the existing updateUI({releaseChannel}) -> 'ui:set' IPC channel and is honored by manual menu checks, the auto 24h background check, focus-resume triggers, and nudge-driven checks. Shift-click stays as a per-click power-user one-shot override.

Today the only way to receive RC builds is Shift-clicking the "Check for Updates" button — undocumented and undiscoverable. Users who want to live on RCs (or want to skip them permanently) had no persisted toggle. This surfaces the option where it belongs, alongside the existing "Check for Updates" row.

Screenshots

Settings > Updates search hit for "updates" now renders the Release Channel row with its Stable / Pre-Release segmented control fully intact — the row is matched at the section-level search catalog as well, so the interactive control no longer disappears from filtered search results.

Settings > Updates with Release Channel control

Changes

  • shared/types: PersistedUIState.releaseChannel?: 'stable' | 'prerelease' (optional; defaults to stable on hydration).
  • main/updater: setupAutoUpdater accepts a new getReleaseChannel callback. A shouldIncludePrerelease(perClick) helper returns true when perClick === true (Shift-click) OR _getReleaseChannel?.() === 'prerelease'. Wired into checkForUpdatesFromMenu (outer opt-in) and runBackgroundUpdateCheck before pinDefaultReleaseFeed so the right tags are fetched. The in-flight requeue branch still keys off options?.includePrerelease === true only (Shift-click) — when the persisted channel is prerelease the in-flight is already RC, so no fresh queue is needed.
  • main/updater (disable path): disableIncludePrerelease() mirrors enableIncludePrerelease(), plus a syncIncludePrereleaseToPersistedChannel() wrapper. Both menu and background checks now enable OR disable the auto-updater's allowPrerelease flag based on the current persisted channel — so a UI revert Pre-Release → Stable takes effect on the very next check instead of waiting for a process restart (this was the gap CodeRabbit flagged in the first review).
  • main/window: store.getUI().releaseChannel feeds the new getReleaseChannel callback at the existing setupAutoUpdater call site.
  • store/slices/ui: normalizeReleaseChannel coerces undefined/hydrated values to the slice literal type. setReleaseChannel short-circuits unchanged values and persists via window.api.ui.set({ releaseChannel }), mirroring the setSetupGuideSidebarDismissed idiom. Hydrate restores explicit 'prerelease' and falls back to 'stable' for pre-feature installs.
  • settings UI: a new SearchableSetting row appended under Updates with a SettingsSegmentedControl (Stable / Pre-Release). Strings go through the standard i18n translate('auto.components.settings.GeneralUpdateSettingsSection.releaseChannel.*') keys (synced into en/es/ja/ko/zh catalogs).
  • settings search: the Release Channel row is registered in getGeneralUpdateSearchEntries and the row's inline keywords now include update / updates / channel so filtering "updates" in the Settings search shows the row WITH its segmented control (instead of hiding the content via mismatched section vs row-level keyword filters).
  • Shift-click affordance in register-app-menu.ts and the Updates button is preserved unchanged as a per-click override.

Behavior matrix

Persisted channel Shift-click Result
stable no stable feed
stable yes RC feed, one-shot
prerelease no RC feed
prerelease yes RC feed, one-shot (no-op equivalent)
session was RC, then reverted UI to stable + next menu check irrelevant stable feed (disable path resets the sticky RC flag)

Testing checklist

  • updater.test.ts — 4 new:
    • Menu check honors persisted prerelease without explicit Shift-click (asserts allowPrerelease === true and the RC feed URL).
    • Startup background check (driven by getLastUpdateCheckAt: () => null) honors persisted prerelease (asserts the RC feed URL and tag fetch count).
    • Persisted stable keeps the stable feed pinned (tag count = 1, allowPrerelease stays false).
    • Revert-to-stable clears a session-sticky RC flag on the next plain menu check (asserts allowPrerelease === false and that the next fetch uses includePrerelease: false).
  • ui.test.ts — 2 new:
    • setReleaseChannel('prerelease') called twice writes window.api.ui.set exactly once (idempotent persistence).
    • Hydrate restores explicit prerelease and falls back to stable when missing.
  • pnpm typecheck clean.
  • pnpm lint clean (incl. switch-exhaustiveness + localization catalog/parity across en/es/ja/ko/zh).
  • pnpm vitest run src/main/updater.test.ts src/main/updater-prerelease-feed.test.ts src/main/updater.check-failure.test.ts src/renderer/src/store/slices/ui.test.ts src/renderer/src/components/settings/GeneralPane.test.ts --config config/vitest.config.ts — 224 tests pass.
  • Manual smoke test in pnpm dev: the Release Channel row appears (with the Stable/Pre-Release segmented control) both when scrolling to Updates directly, and when filtering on "updates" via the Settings search bar.

AI Review Report

CodeRabbit ran an automated review across the two updated commits (179c2bd85b47e7) and afterwards re-ran for the search-index fix (2557a2f). Findings:

  • 🟠 Major — Functional Correctness (auto-resolved): enableIncludePrerelease() lacks a disable path, so reverting to Stable is ignored until restart. Fixed by adding disableIncludePrerelease() + syncIncludePrereleaseToPersistedChannel() in commit 85b47e79f — both menu and background checks now mirror the persisted channel onto the auto-updater flag. New regression test clears a session-sticky RC flag when a plain menu check runs after the channel reverts to stable covers the disable path.
  • 🚥 Pre-merge warnings (informational):
    • Description check — original description did not match the repo template. This body rewrite adds the required Screenshots, Testing checklist, AI Review Report, Security Audit, and Notes sections.
    • Docstring coverage — 33.33% reported, below the 80% target. All new functions added by this PR carry JSDoc comments (shouldIncludePrerelease, disableIncludePrerelease, syncIncludePrereleaseToPersistedChannel, normalizeReleaseChannel); the residual delta is from pre-existing code in the touched files, not from these additions.

No outstanding actionable threads remain on the PR.

Security Audit

  • No new IPC channels or persistence surfaces. The feature reuses the existing Store.updateUI({...}) -> 'ui:set' -> 'ui:stateChanged' pipeline; the persisted releaseChannel value is one of two closed-set literals ('stable' | 'prerelease') and validates to 'stable' on the read path when corrupted or missing.
  • No secrets, URLs, or credentials touched. The auto-updater still resolves under stablyai/orca releases only; no new download domains or signature-skip paths introduced.
  • No new network surfaces. Prerelease opt-in uses the already-trusted GitHub generic provider manifest probe; no additional HTTP traffic, just a different release tag set eligible for resolution.
  • No shell/system-call surface touched by this change.

Cross-platform impact: none — no platform branches touched, no menu accelerators changed, no metaKey/ctrlKey shortcuts added or removed. SSH use case unaffected (the channel selection is a renderer/main-process setting; it governs the host Orca app's own auto-updater, not the SSH session lifecycle).

Side effects / migrations

None. The new field is optional and defaults to stable; pre-feature installs hydrate as stable and behave unchanged. No IPC plumbing additions, no schema migration — Orca's existing updateUI merge + ui:stateChanged re-broadcast handles the new key automatically.

Notes

  • The persisted Release Channel is orthogonal to the Shift-click one-shot — the two operate on different layers. Shift-click wins per-click (and stays as-is for power users); the persisted channel wins between clicks (the next background check, including the 24h timer and powerMonitor resume, resets to the persisted choice).
  • Three commits land the feature: 179c2bd (feat — UI + persistence + updater honoring), 85b47e7 (fix — CodeRabbit's 🟠 disable path), 2557a2f (fix — Settings search index + row keywords so "updates" search shows the segmented control). All three should land together for a single functional unit.

…tings > Updates

Add a Release Channel segmented control (Stable / Pre-Release) under
Settings > Updates so users can opt into release-candidate builds
without the hidden Shift-click menu shortcut. The choice persists via
the existing  ->  IPC channel
and is honored by both manual menu checks and the auto background
check (24h timer, focus resume, nudge-driven).

- shared/types: add  to PersistedUIState.
- main/updater: inject a  callback at setupAutoUpdater
  and consult it through , which opts
  into the RC feed when the persisted channel is 'prerelease' OR a
  Shift-click one-shot fires. runBackgroundUpdateCheck enables
  prerelease before pinDefaultReleaseFeed so the right tags are fetched.
- main/window: store.getUI().releaseChannel feeds the new callback.
- store/slices/ui: hydrate the persisted channel via normalizeReleaseChannel;
  setReleaseChannel short-circuits unchanged writes and persists via
  window.api.ui.set, mirroring the setupGuideSidebarDismissed pattern.
- settings: new SearchableSetting row with SettingsSegmentedControl.
- Shift-click affordance in register-app-menu is preserved as a
  power-user one-shot that overrides a single check.

Tests:
- updater.test.ts: persisted 'prerelease' drives a menu check, the
  startup background check, and a stable-channel check onto the right
  feed tags. Feed URL and allowPrerelease assertions mirror the
  existing Shift-click test.
- updater.test.ts: persisted 'prerelease' is honored on the startup
  background check.
- updater.test.ts: persisted 'stable' keeps the stable feed pinned.
- ui.test.ts: idempotent persistence (call once, no double-write);
  hydration restores explicit 'prerelease' and falls back to 'stable'.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds persisted releaseChannel support across shared UI state, the settings screen, and the auto-updater. The UI store now normalizes, hydrates, and persists stable/prerelease selections. The settings page shows a Stable/Pre-Release control with localized strings. The main process reads the stored channel and uses it for menu and background update checks. Tests cover store and updater behavior.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 is concise and accurately summarizes the main change: a persisted release-channel selector in Settings > Updates.
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 follows the repository template and includes the required summary, screenshots, testing, review, security, and notes sections.

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


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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: db7ef256-c102-41fb-afcb-c4ae0a9a7c78

📥 Commits

Reviewing files that changed from the base of the PR and between f6ad569 and 179c2bd.

📒 Files selected for processing (12)
  • src/main/updater.test.ts
  • src/main/updater.ts
  • src/main/window/attach-main-window-services.ts
  • src/renderer/src/components/settings/GeneralUpdateSettingsSection.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/store/slices/ui.test.ts
  • src/renderer/src/store/slices/ui.ts
  • src/shared/types.ts

Comment thread src/main/updater.ts
gatsby74 added 2 commits June 25, 2026 14:36
…ts to stable

CodeRabbit (🟠 Major) on PR stablyai#6359: once `includePrereleaseActive` becomes
true (via Shift-click or the persisted Pre-Release channel), there was
no disable path — switching the channel back to 'stable' in Settings >
Updates left subsequent menu and background checks still consuming
release-candidate tags until the process restarted.

Add `disableIncludePrerelease()` mirroring `enableIncludePrerelease()`
and a `syncIncludePrereleaseToPersistedChannel()` wrapper. Both the
menu and the background check now mirror the persisted channel onto the
auto-updater's `allowPrerelease` flag before pinning the release feed:

- Shift-click (`includePrerelease:true`) still wins as a per-click
  override and calls `enableIncludePrerelease` directly.
- A plain menu check or background check now enables OR disables based
  on the persisted channel, so a UI revert Pre-Release -> Stable takes
  effect on the very next check.

Test:
- New: 'clears a session-sticky RC flag when a plain menu check runs
  after the channel reverts to stable' — captures the regression from
  CodeRabbit's sticky-state analysis: Shift-click sets
  autoUpdater.allowPrerelease = true, the persisted channel then flips
  to 'stable', the next plain menu check resets the flag and fetches
  the stable feed (tag count = 1, includePrerelease = false).

Verification:
- pnpm typecheck clean.
- pnpm lint clean (incl. switch-exhaustiveness + localization catalog/parity).
- pnpm vitest run src/main/updater.test.ts --config config/vitest.config.ts
  -> 76 tests pass (added 1, total +1 vs prior commit's 75).
Searching "updates" in Settings revealed the Updates section (because the
section-level check matched the existing "Check for Updates" entry) but the
Release Channel SearchableSetting returned null — its own inline keywords
list omitted "update"/"updates"/"channel", so the row was hidden while the
section was visible, leaving the title appearing without its segmented
control.

- inline keywords on the Release Channel SearchableSetting now include
  update / updates / channel in addition to release channel / stable /
  prerelease / rc / release candidate / beta.
- getGeneralUpdateSearchEntries now includes the Release Channel row
  (title + description + matching localized keyword set) so section-level
  filter logic matches against the row content as well.

Verification:
- pnpm typecheck clean, pnpm lint clean (incl. localization catalog/parity).
- 224 tests pass (updater + ui slice + GeneralPane).
- Dev-run confirms searching "updates" / "channel" now renders the
  Release Channel row with its Stable / Pre-Release segmented control.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

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.

2 participants