Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exclude = [
resolver = "2"

[workspace.package]
version = "4.37.0"
version = "4.37.0-alpha"
edition = "2024"
Comment on lines 15 to 17
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

This change still leaves the app version duplicated between the workspace Cargo.toml and src-tauri/tauri.conf.json. Since GUI version displays read from Tauri’s getVersion(), the drift problem from #470 can recur unless there’s an automated sync/CI guard; consider adding a pre-build script (or CI check) that updates/validates tauri.conf.json from the workspace version so changing stage/version is truly single-source-of-truth.

Copilot uses AI. Check for mistakes.
authors = ["Conductor Contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion conductor-gui/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"productName": "Conductor",
"version": "4.26.6",
"version": "4.37.0-alpha",
"identifier": "com.amiable.conductor",
"build": {
"beforeDevCommand": "npm --prefix ui run dev",
Expand Down
37 changes: 37 additions & 0 deletions conductor-gui/ui/src/lib/components/TitleBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@
-->

<script>
import { onMount } from 'svelte';
import { getVersion } from '@tauri-apps/api/app';
import { deviceBindingsStore, profileStore } from '$lib/stores.js';
import { workspaceView, WORKSPACE_VIEWS } from '$lib/stores/workspace.js';
import { parseReleaseStage } from '$lib/utils/release-stage';

let releaseStage = null;

onMount(async () => {
try {
const version = await getVersion();
releaseStage = parseReleaseStage(version);
} catch {
releaseStage = null;
}
});

// Track selected values from store state
$: selectedDeviceId = $deviceBindingsStore.bindings?.[0]?.device_id || '';
Expand All @@ -34,6 +48,11 @@
<div class="titlebar">
<div class="titlebar-left">
<span class="logo">CONDUCTOR</span>
{#if releaseStage}
<span class="release-stage" class:stage-alpha={releaseStage === 'alpha'} class:stage-beta={releaseStage === 'beta'}>
{releaseStage.toUpperCase()}
</span>
{/if}
</div>

<div class="titlebar-right">
Expand Down Expand Up @@ -85,6 +104,24 @@
letter-spacing: 1px;
}

.release-stage {
font-size: 9px;
font-weight: 700;
letter-spacing: 1px;
padding: 2px 6px;
border-radius: var(--radius-sm);
color: var(--bg-app);
text-transform: uppercase;
}

.release-stage.stage-alpha {
background: var(--amber);
}

.release-stage.stage-beta {
background: var(--blue);
}

.titlebar-right {
display: flex;
align-items: center;
Expand Down
37 changes: 36 additions & 1 deletion conductor-gui/ui/src/lib/components/TitleBar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent } from '@testing-library/svelte';

const { mockSwitchTo } = vi.hoisted(() => ({
const { mockSwitchTo, mockGetVersion } = vi.hoisted(() => ({
mockSwitchTo: vi.fn(() => Promise.resolve({ success: true })),
mockGetVersion: vi.fn(() => Promise.resolve('4.37.0-alpha')),
}));

vi.mock('@tauri-apps/api/app', () => ({
getVersion: mockGetVersion,
}));

vi.mock('$lib/stores.js', async () => {
Expand Down Expand Up @@ -88,4 +93,34 @@ describe('TitleBar', () => {
await fireEvent.change(profileDropdown, { target: { value: 'p2' } });
expect(mockSwitchTo).toHaveBeenCalledWith('p2');
});

it('shows ALPHA release stage badge when version is alpha', async () => {
mockGetVersion.mockResolvedValue('4.37.0-alpha');
const TitleBar = (await import('./TitleBar.svelte')).default;
render(TitleBar);
// Wait for onMount async to complete and Svelte to re-render
await vi.waitFor(() => {
expect(screen.getByText('ALPHA')).toBeTruthy();
}, { timeout: 2000 });
});

it('shows BETA release stage badge when version is beta', async () => {
mockGetVersion.mockResolvedValue('4.37.0-beta');
const TitleBar = (await import('./TitleBar.svelte')).default;
render(TitleBar);
await vi.waitFor(() => {
expect(screen.getByText('BETA')).toBeTruthy();
}, { timeout: 2000 });
});

it('does not show release stage badge for production version', async () => {
mockGetVersion.mockResolvedValue('4.37.0');
const TitleBar = (await import('./TitleBar.svelte')).default;
render(TitleBar);
// Wait for Svelte to update DOM and ensure no release stage badge is shown
await vi.waitFor(() => {
expect(screen.queryByText('ALPHA')).toBeNull();
expect(screen.queryByText('BETA')).toBeNull();
}, { timeout: 2000 });
});
});
43 changes: 43 additions & 0 deletions conductor-gui/ui/src/lib/utils/release-stage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Release stage utility tests (#512)
*/

import { describe, it, expect } from 'vitest';
import { parseReleaseStage } from './release-stage';

describe('parseReleaseStage', () => {
it('returns "alpha" for alpha pre-release', () => {
expect(parseReleaseStage('4.37.0-alpha')).toBe('alpha');
});

it('returns "beta" for beta pre-release', () => {
expect(parseReleaseStage('4.37.0-beta')).toBe('beta');
});

it('returns null for production (no pre-release)', () => {
expect(parseReleaseStage('4.37.0')).toBe(null);
});

it('returns null for empty string', () => {
expect(parseReleaseStage('')).toBe(null);
});

it('returns null for "dev"', () => {
expect(parseReleaseStage('dev')).toBe(null);
});

it('is case-insensitive', () => {
expect(parseReleaseStage('4.37.0-ALPHA')).toBe('alpha');
expect(parseReleaseStage('4.37.0-Beta')).toBe('beta');
});

it('returns null for unrecognised pre-release tags', () => {
expect(parseReleaseStage('4.37.0-rc1')).toBe(null);
expect(parseReleaseStage('4.37.0-snapshot')).toBe(null);
});

it('handles version with dotted pre-release identifier', () => {
expect(parseReleaseStage('4.37.0-alpha.1')).toBe('alpha');
});
});

30 changes: 30 additions & 0 deletions conductor-gui/ui/src/lib/utils/release-stage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Release stage utilities — parse alpha/beta/prod from semver version strings.
*
* This helper is agnostic about where the version string comes from; it simply
* inspects the pre-release suffix (e.g., "4.37.0-alpha" or "4.37.0-beta") to
* determine the release stage. Callers are responsible for ensuring version
* values are consistent across Cargo.toml, tauri.conf.json (used by Tauri's
* getVersion()), and any other sources.
*/

export type ReleaseStage = 'alpha' | 'beta' | null;

/**
* Extract the release stage from a semver version string.
*
* "4.37.0-alpha" → "alpha"
* "4.37.0-beta" → "beta"
* "4.37.0" → null (production)
* "dev" → null
*/
export function parseReleaseStage(version: string): ReleaseStage {
if (!version) return null;
const match = version.match(/-(\w+)/);
if (!match) return null;
const stage = match[1].toLowerCase();
if (stage === 'alpha') return 'alpha';
if (stage === 'beta') return 'beta';
return null;
}

Loading