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
5 changes: 5 additions & 0 deletions .github/workflows/release-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ jobs:
- os: windows-latest
portable-artifact: gridfpv-windows-portable
portable-path: src-tauri/target/release/gridfpv-desktop.exe
- os: macos-latest
# Apple Silicon (macos-latest = arm64) — covers every modern Mac; an Intel
# (x86_64) leg is a matrix add if one ever turns up at the field.
portable-artifact: gridfpv-macos-portable
portable-path: src-tauri/target/release/gridfpv-desktop

steps:
- uses: actions/checkout@v7
Expand Down
18 changes: 9 additions & 9 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ exclude = ["src-tauri"]
# Shared metadata + dependency versions for every crate in the spine.
# Crates inherit these with `field.workspace = true` so versions are set once.
[workspace.package]
# The ONE product version (v0.4.0-alpha.1 scheme: 0.x milestones, -alpha/-beta pre-releases;
# bump with `cargo xtask version <x.y.z[-pre]>`). Crates inherit it; tauri.conf.json and the
# console package.json are kept in lock-step by the same xtask command.
version = "0.4.0-alpha.1"
edition = "2024"
license = "AGPL-3.0-or-later"
repository = "https://gitlab.com/gridfpv/gridfpv"
repository = "https://github.com/GridFPV/gridfpv"
rust-version = "1.85"

[workspace.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion crates/adapters/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-adapters"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-app"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-engine"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/events/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-events"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/projection/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-projection"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-server"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
14 changes: 14 additions & 0 deletions crates/server/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,20 @@ pub fn router(registry: EventRegistry) -> Router {
// event id to its own `AppState`/log through the registry.
let read = Router::new()
.route("/health", get(|| async { "ok" }))
// The product identity (alpha field-support): WHICH build is this rig running? The
// console footer reads it, and a bug report from the field should quote it. The
// version is the ONE workspace version (v0.4.0-alpha.1 scheme, `cargo xtask version`);
// the contract version is the independent wire-compat integer.
.route(
"/about",
get(|| async {
Json(serde_json::json!({
"name": "GridFPV",
"version": env!("CARGO_PKG_VERSION"),
"contract_version": crate::CONTRACT_VERSION,
}))
}),
)
// The Director's wall clock, in epoch microseconds (open, no auth). The console measures its
// offset from this (round-trip-corrected) so the start countdown + race clock read off
// *server* time, not the RD device's clock — which can differ by ~1s on a separate laptop and
Expand Down
2 changes: 1 addition & 1 deletion crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-storage"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/testkit/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gridfpv-testkit"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion frontend/apps/rd-console/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gridfpv/rd-console",
"version": "0.0.0",
"version": "0.4.0-alpha.1",
"private": true,
"description": "GridFPV RD console surface — control-oriented, dense, authenticated. Minimal shell; filled by #51+.",
"license": "AGPL-3.0-or-later",
Expand Down
27 changes: 27 additions & 0 deletions frontend/apps/rd-console/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,17 @@
}

// ── Settings (the RD token, set/cleared up front) ──────────────────────────
// The running Director's identity for the footer build-stamp (alpha field-support: a bug
// report should quote the version). Same-origin fetch — the console is always served BY the
// Director; a failed read leaves the footer blank rather than lying.
let about = $state<{ version: string } | undefined>(undefined);
$effect(() => {
fetch('/about')
.then((r) => (r.ok ? r.json() : undefined))
.then((a) => (about = a))
.catch(() => {});
});

// v1 keeps NO manual settings surface: the control token is requested automatically by
// TokenDialog when a privileged action needs one (loopback needs none at all), and the
// setup wizard runs once on event creation (everything it sets stays editable on the
Expand Down Expand Up @@ -375,9 +386,25 @@
<!-- The lazy token prompt lives above any screen (the ONLY token surface in v1). -->
<TokenDialog bind:this={tokenDialog} />

{#if about}
<footer class="build-stamp" aria-label="Build version">GridFPV v{about.version}</footer>
{/if}

<ToastHost />

<style>
.build-stamp {
position: fixed;
right: var(--gf-space-3);
bottom: var(--gf-space-2);
z-index: 5;
font-family: var(--gf-font-family);
font-size: var(--gf-font-size-2xs);
color: var(--gf-text-muted);
opacity: 0.7;
pointer-events: none;
}

/* The active-event resume loading state (#90). */
.resume-loading {
display: flex;
Expand Down
1 change: 0 additions & 1 deletion frontend/apps/rd-console/tests/AudioHost.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
let { session }: { session: Session } = $props();
// The controller binds to ONE session for its lifetime (exactly like App.svelte's mount);
// tests never swap the prop, so capturing the initial value is the intent.
// eslint-disable-next-line svelte/valid-compile
// svelte-ignore state_referenced_locally
mountRaceDayAudio(session);
</script>
12 changes: 12 additions & 0 deletions frontend/contract/events.contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1662,3 +1662,15 @@ describe('race Slice 3a: FillRound (round-driven engine)', () => {
expect(res.status).toBe(401);
});
});

describe('seam: /about (the build stamp)', () => {
it('serves the product version + the wire contract version', async () => {
const res = await fetch(`${director.baseUrl}/about`);
expect(res.ok).toBe(true);
const about = await res.json();
expect(about.name).toBe('GridFPV');
// x.y.z with an optional -prerelease tail (the v0.4.0-alpha.1 scheme).
expect(about.version).toMatch(/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/);
expect(typeof about.contract_version).toBe('number');
});
});
14 changes: 10 additions & 4 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
# Build it separately with: cd src-tauri && cargo tauri build
[package]
name = "gridfpv-desktop"
version = "0.1.0"
# Kept in lock-step with the ROOT workspace version by `cargo xtask version` (this crate is
# a standalone workspace and cannot inherit it).
version = "0.4.0-alpha.1"
edition = "2024"
license = "AGPL-3.0-or-later"
repository = "https://github.com/GridFPV/gridfpv"
Expand All @@ -35,14 +37,18 @@ tauri-build = { version = "2", features = [] }

[dependencies]
# The Director itself: the native app runs the SAME `gridfpv_app::director::run_director` the
# hosted `gridfpv` binary runs — in-process, on a tokio task, bound to loopback. No `live`
# feature (sim source only), so the desktop build pulls no openssl, exactly like the Director.
# hosted `gridfpv` binary runs — in-process, on a tokio task, bound to loopback.
#
# `live` compiles in the REAL RotorHazard adapter — the product's whole job is talking to
# real timers, and the alpha field test runs against real RH hardware, so the desktop builds
# carry it by default (v0.4.0-alpha.1 decision). The TLS stack it pulls is VENDORED
# (self-contained native-tls), so the portable stays a single file with no system OpenSSL.
#
# `embed-assets` bakes the built RD console `dist/` into THIS binary at compile time, so the
# produced `gridfpv-desktop` executable is **self-contained** — it serves the console from
# memory and needs no external dist folder beside it. That makes the raw binary a single-file
# portable, and lets the installers drop the previously-bundled `rd-console-dist` resource.
gridfpv-app = { path = "../crates/app", features = ["embed-assets"] }
gridfpv-app = { path = "../crates/app", features = ["embed-assets", "live"] }

tauri = { version = "2", features = [] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync"] }
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "GridFPV",
"version": "0.1.0",
"version": "0.4.0-alpha.1",
"identifier": "org.gridfpv.desktop",
"build": {
"beforeBuildCommand": "cd frontend && npm ci && npm run build",
Expand Down
2 changes: 1 addition & 1 deletion xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xtask"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
Loading