feat(desktop): persisted release/preview channel selection with deliberate manifest publication - #6268
Conversation
…erate manifest publication Shell: persist typed release/preview channel preference and return it with migration state. Bootstrap: select and probe the persisted channel in its single IPC roundtrip. Client: remember remote-shell channel switches and provide a localized return-to-release badge. CI: publish the updater manifest only through an explicit, validated dispatch workflow.
| TAG: ${{ inputs.tag }} | ||
| GH_TOKEN: ${{ github.token }} | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
P1: Workflow uses unpinned GitHub Actions in sensitive publish workflow
Publish workflow references actions by mutable tag instead of pinned SHA.
Pin actions/checkout and actions/setup-node to full commit SHAs with version comments.
AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.
<file name=".github/workflows/publish-shell-manifest.yml">
<violation number="1" location=".github/workflows/publish-shell-manifest.yml:26">
<priority>P1</priority>
<title>Workflow uses unpinned GitHub Actions in sensitive publish workflow</title>
<evidence>The publish-shell-manifest.yml workflow references actions by mutable version tags (actions/checkout@v4 at line 26 and actions/setup-node@v4 at line 43) instead of full-length commit SHAs. This workflow handles the CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID secrets, and a tag reference can be moved to point at a different commit, so a compromised or moved tag could run untrusted code with access to those credentials.</evidence>
<recommendation>Pin both actions to full-length commit SHAs with version comments, e.g. uses: actions/checkout@<full-sha> # v4.1.1 and uses: actions/setup-node@<full-sha> # v4.0.3. SHA-pinning prevents a moved tag from running untrusted code with access to the Cloudflare publish credentials.</recommendation>
</violation>
</file>
There was a problem hiding this comment.
Code Review
This pull request introduces support for persisting and navigating between different remote content channels ('release' and 'preview') in the Tauri desktop application. It updates the bootstrap process, adds Rust-side commands to store and retrieve channel preferences, and introduces a PreviewBadge component in the frontend for channel switching. Feedback focuses on adhering to the repository's architectural rule R2 by replacing boolean fields (such as remote_load_ok) with typed enums or unions in both Rust and TypeScript, and optimizing the PreviewBadge component to avoid instantiating new URL on every render.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| pub struct StashLegacyStorageResult { | ||
| pub remote_load_ok: bool, | ||
| pub channel: Channel, | ||
| } |
There was a problem hiding this comment.
[MEDIUM] Avoid bool fields on structs per R2. Evidence: client/src-tauri/src/migration.rs:26-29.
Why it matters: Boolean fields do not express the design space as clearly as typed enums and violate the repository's hard architectural rules.
Suggested fix: Replace remote_load_ok: bool with a typed enum such as RemoteLoadStatus.
/// The remote load status of the shell.
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RemoteLoadStatus {
#[default]
Pending,
Success,
}
/// The bootstrap's one-roundtrip migration and remote-navigation state.
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct StashLegacyStorageResult {
pub remote_load_status: RemoteLoadStatus,
pub channel: Channel,
}References
- A
boolfield never expresses the design space; the project uses typed enums likeControllerRef,Comparator,PlayerScope,Option<T>, and dedicated discriminated unions instead. (link)
| let remote_load_ok = stage_legacy_storage_marker(files, json)?; | ||
| Ok(StashLegacyStorageResult { | ||
| remote_load_ok, | ||
| channel: read_channel_preference(files), | ||
| }) |
There was a problem hiding this comment.
[MEDIUM] Update stage_legacy_storage to use the new RemoteLoadStatus enum. Evidence: client/src-tauri/src/migration.rs:131-135.
Why it matters: This aligns the implementation with the R2-compliant RemoteLoadStatus enum instead of using a raw boolean.
Suggested fix: Map the boolean result of stage_legacy_storage_marker to the RemoteLoadStatus enum.
let remote_load_ok = stage_legacy_storage_marker(files, json)?;
let remote_load_status = if remote_load_ok {
RemoteLoadStatus::Success
} else {
RemoteLoadStatus::Pending
};
Ok(StashLegacyStorageResult {
remote_load_status,
channel: read_channel_preference(files),
})| interface LegacyStorageResult { | ||
| remote_load_ok: boolean; | ||
| channel: Channel; | ||
| } |
There was a problem hiding this comment.
[MEDIUM] Avoid bool fields in shared state per R2. Evidence: client/bootstrap/bootstrap.ts:13-16.
Why it matters: Using a boolean field for remote load status violates the repository's architectural rule R2 and doesn't scale as well as a typed enum.
Suggested fix: Replace remote_load_ok: boolean with a typed enum/union RemoteLoadStatus = "pending" | "success".
| interface LegacyStorageResult { | |
| remote_load_ok: boolean; | |
| channel: Channel; | |
| } | |
| type RemoteLoadStatus = "pending" | "success"; | |
| interface LegacyStorageResult { | |
| remote_load_status: RemoteLoadStatus; | |
| channel: Channel; | |
| } |
References
- A
boolfield never expresses the design space; the project uses typed enums likeControllerRef,Comparator,PlayerScope,Option<T>, and dedicated discriminated unions instead. (link)
| const { remote_load_ok: remoteLoadOk, channel } = await stashLegacyStorage(); | ||
| const destination = channelUrl(channel); | ||
|
|
||
| if (await remoteLoadSucceededBefore()) { | ||
| location.replace(channelUrl); | ||
| if (remoteLoadOk) { | ||
| location.replace(destination); | ||
| return; | ||
| } |
There was a problem hiding this comment.
[MEDIUM] Update the bootstrap navigation check to use the new remote_load_status enum. Evidence: client/bootstrap/bootstrap.ts:58-64.
Why it matters: This aligns the client-side bootstrap with the R2-compliant RemoteLoadStatus enum.
Suggested fix: Check if remote_load_status is "success" instead of a boolean.
| const { remote_load_ok: remoteLoadOk, channel } = await stashLegacyStorage(); | |
| const destination = channelUrl(channel); | |
| if (await remoteLoadSucceededBefore()) { | |
| location.replace(channelUrl); | |
| if (remoteLoadOk) { | |
| location.replace(destination); | |
| return; | |
| } | |
| const { remote_load_status, channel } = await stashLegacyStorage(); | |
| const destination = channelUrl(channel); | |
| if (remote_load_status === "success") { | |
| location.replace(destination); | |
| return; | |
| } |
| const isPreviewRemoteShell = | ||
| isRemoteTauriShell && window.location.origin === new URL(__PREVIEW_SITE_URL__).origin; |
There was a problem hiding this comment.
[MEDIUM] Avoid instantiating new URL on every render of PreviewBadge. Evidence: client/src/components/chrome/PreviewBadge.tsx:34-35.
Why it matters: Creating a new URL object on every render is inefficient and can be easily avoided since __PREVIEW_SITE_URL__ is a static string with no trailing slash or path.
Suggested fix: Directly compare window.location.origin with __PREVIEW_SITE_URL__.
| const isPreviewRemoteShell = | |
| isRemoteTauriShell && window.location.origin === new URL(__PREVIEW_SITE_URL__).origin; | |
| const isPreviewRemoteShell = | |
| isRemoteTauriShell && window.location.origin === __PREVIEW_SITE_URL__; |
Shell: persist typed release/preview channel preference and return it with migration state.
Bootstrap: select and probe the persisted channel in its single IPC roundtrip.
Client: remember remote-shell channel switches and provide a localized return-to-release badge.
CI: publish the updater manifest only through an explicit, validated dispatch workflow.