Skip to content

Cross-Platform GUI

The desktop GUI is one of the toolkit's three user-facing interfaces alongside the CLI and MCP server. Its destination is one open-source editor for Quad Cortex and Nano Cortex on Linux, Windows and macOS over the same Rust transport and host foundation, not separate applications that reimplement device behaviour. Shared infrastructure will remain shared, while the Quad's grid and the Nano's fixed signal chain retain honest device-specific domain models and screens.

The current first draft supports working Quad and Nano surfaces through one managed Rust backend. Quad mode reads status, grid, active scene, CPU and populated preset slots and exposes non-persistent recall, scene, parameter and bypass controls. Nano mode renders the fixed eight-role signal chain, exposes explicit Apply controls for the five raw amp values and Gate reduction from the typed, paced daemon snapshot, and provides an FX parameter inspector; it deliberately does not force those roles into the Quad grid model. Neither mode opens a second HID connection.

Hardware evidence remains recorded in the roadmap and in the Rust-owned, host-aware capability matrix (gui/src-tauri/src/capability.rs), where an unknown operation defaults to unverified. The ordinary player workflow does not repeat development-maturity badges beside each control. Unavailable operations and device refusals remain explicit at the point of action; detailed host and command-contract evidence belongs in engineering documentation, diagnostics and release notes.

Linux is the first and only hardware-verified baseline today because Neural DSP has not provided Cortex Control for Linux and this community project began by filling that gap for ourselves. Unix-domain IPC and Windows local-only named-pipe IPC are implemented, and the release pipeline builds native .deb, .dmg and NSIS .exe tester previews. The first native remote package matrix passed on 2026-08-30. Windows and macOS device behavior remains unverified, so their packages remain provisional rather than supported.

Windows and macOS host work is active rather than merely planned. Windows now has a native named-pipe daemon/process boundary and unsigned current-user NSIS package configuration; macOS has Apple Silicon DMG configuration with ad-hoc signing. Release-preview run 33317851191 passed native no-hardware host tests and package verification for Linux, macOS and Windows. The Windows tester-preview smoke covers direct hardware and the preferred first-pass QEMU/KVM setup. Windows hardware smoke and a corresponding native macOS package/device smoke remain outstanding.

It adapts the mockable IPC-boundary architecture of rixrix/deskop-nano-cortex (Apache-2.0), while independently implementing the Quad-specific model and Mantine presentation. See NOTICE and THIRD-PARTY-NOTICES.md.

Choosing a mode

Install the platform's Tauri prerequisites, then install frontend dependencies in gui/.

  • Daemon-backed mode, the real thing: run s/gui-dev from anywhere in the repository. The GUI reuses existing compatible product-scoped held sessions or starts missing auto-managed sessions itself, trying Quad and then Nano when no session exists. The header device selector can explicitly choose Quad, Nano, or Auto-detect; if both products are connected, Auto-detect selects Quad, and switching products leaves the other product's held session live.
  • Browser fixture mode, for frontend development without hardware: run npm run dev or npm run dev:fixture inside gui/. This serves fictional development data in an ordinary browser tab, with no Tauri backend and no daemon. Add ?device=nano to the browser URL for the fictional Nano fixture.
  • Run npm run check inside gui/ to type-check and build both adapters.

The mode is never inferred from success or failure. A daemon error remains an error; Tauri mode does not fall back to plausible-looking fixture state. The header identifies fixture mode explicitly with a yellow banner.

Window size

The native GUI starts at a 1440x900 logical-pixel 16:10 reference size and remains manually resizable with a 1080x675 minimum. Its header's Window size menu offers 75%, 100%, 125%, 150%, and 200% 16:10 presets. These resize the native Tauri window rather than changing browser zoom, text size, or device state. Arbitrary aspect ratios remain supported, so the editor moves supporting work below the topology where necessary instead of assuming a fixed ratio.

Tester-preview packages

The native package pipeline is configured to build a Linux x86_64 .deb, an Apple Silicon .dmg, and a Windows x86_64 NSIS .exe. Each desktop package embeds the matching cortex CLI beside the GUI because that process owns the held USB session; it does not embed cortex-mcp. The Linux package installs and activates the two-device udev rule. Published GUI filenames end in -preview, release notes repeat their provisional status, and all downloads share one SHA256SUMS file that should be verified before installation.

  • Linux x86_64: sha256sum --check --ignore-missing SHA256SUMS, then sudo apt install ./Cortex_<version>_amd64-preview.deb. The package reloads udev rules and triggers existing hidraw devices; unplug/replug if the desktop session still has no ACL. This is the only host with real-device evidence.
  • macOS arm64: set package=Cortex_<version>_aarch64-preview.dmg, then run grep " $(basename "$package")$" SHA256SUMS | shasum -a 256 -c -; open the .dmg and drag Cortex into Applications. The package is ad-hoc signed, not Developer ID signed or notarized; macOS may require an explicit approval in Privacy & Security. Device and daemon behavior remain provisional.
  • Windows x86_64: compare Get-FileHash -Algorithm SHA256 with SHA256SUMS, then run the Cortex_<version>_x64-setup-preview.exe NSIS installer. It is unsigned and Windows SmartScreen may warn. Installing an upgrade or uninstalling closes only the GUI at that installation path and refuses to continue if any daemon or direct CLI operation still locks the bundled helper; it never stops or kills CLI/session processes implicitly. Native CI passed named-pipe isolation, daemon lifecycle, silent install, upgrade and uninstall without hardware; USB/HID behavior is provisional.

The GUI exposes no persistent save control on any platform. Report the operating-system version, package filename, device model, CorOS version, and whether startup, reconnect, reads and one reversible working-copy edit succeeded. Do not attach raw USB captures or logs containing serial numbers, preset names or other owner-specific device data.

Daemon snapshots carry physical-session generation and cache revisions. Reconnecting, failed, incomplete and invalidated state hides the old live grid rather than presenting it as current. During reconnect the GUI shows the real daemon attempt count and last error; Reconnect now interrupts the current automatic backoff without bypassing the full subscribed handshake. A generation change also clears the selected block. CPU may initially say that it is awaiting a device push because the first subscribed CPU report is delayed.

What the GUI does today

Everything below is non-persistent: it changes the working copy the unit is currently playing, exactly as pressing a footswitch or turning a knob on the device does, and saves nothing to storage. There is no save control in the GUI. Preset saving remains a CLI operation until its own confirmation UX, restoration semantics and hardware-tested safety contract are implemented here (tracked as GUI-004.1).

Preset directory and recall

In Quad mode, the sidebar lists every setlist and slot the daemon can read, grouped by setlist. The search field filters that already-loaded directory locally by preset name or displayed slot, without another daemon request; matching is case-insensitive, empty setlists disappear from the results, and clearing the query restores the complete current directory. Clicking a slot recalls it: the unit loads that preset as the new working copy, replacing whatever was there before, exactly as pressing the preset button on the unit does. Recall is offered without confirmation because, unlike save, it writes nothing to the unit's storage - only the working copy changes, and the previous working copy was itself never saved by the GUI. Nano preset inventory and recall are not yet implemented, so Nano mode does not reserve an empty Quad-only sidebar.

The clicked slot shows "Recalling..." while in flight; the whole directory is disabled and the search query becomes read-only until it completes, so neither a second click nor a changed filter can hide or race the pending operation. Search result changes are announced, and the no-match state is also shown visually. Nothing is updated optimistically - the sidebar's active-preset highlight and the working-grid title reflect what the device reports back after the recall, not the slot that was clicked. A failed recall surfaces as a refresh error rather than a silent no-op.

Scene selector

A radio group across the unit's eight scenes (A-H) lets you switch which scene is active. Switching a scene is a real, audible change to what the unit is currently playing; nothing is saved. The active selection, letter and label are always read back from the device rather than assumed, so a switch that the unit refuses or redirects elsewhere shows up as the device's actual answer, not the one requested.

A scene change reported by the device - including one made from the unit itself, such as a footswitch press - is announced to a screen reader too, without moving focus away from whatever control you are using (offline-verified only; see GUI-006.3 in spec/roadmap.md).

Renaming and recolouring the active scene are also available, with a full RGB colour picker rather than the unit's fixed eight-colour palette - hardware confirmed accepting and rendering arbitrary RGB on 2026-08-16 (see spec/roadmap.md for the underlying evidence). Both edits are offline-verified only; a GUI rename or recolour appearing correctly on the physical unit has not yet been separately confirmed. A blank name clears the label rather than writing an empty string.

Copy and swap between scenes are not yet implemented.

Parameter inspector

Selecting a block on the grid opens its inspector: model name, category, position, and (if the model carries one) Neural DSP's own attribution string for what it is evocative of. A bypass switch engages or bypasses the selected block; like every edit here, bypass reaches the active scene only, because that is how the device stores it, and the inspector says so.

Below that, every editable parameter the block's catalog entry describes gets its own control - a slider and number input in real units (dB, ms, Hz, etc.) where the catalog gives a usable range, a dropdown for a named-step switch, or a text field for a string parameter. Read-only meters are shown but not editable. Each control carries the catalog payload generation/revision, model id, canonical catalog name, positional wire index and kind/range/units/steps descriptor returned by Rust; the device path rechecks every field before converting real units or sending a write, so replacing the catalog or selected model cannot silently apply an old range or control to a different parameter. A write is followed by a device read-back, so a clamped or refused value shows what the unit actually holds rather than what was asked for.

Parameter reads and edits are hardware-verified for the current contracts on CorOS 4.0.1: the CLI/daemon path (2026-09-14, three catalog-converted DISTORTION writes 5.0 -> 2.5 -> 5.0 each confirmed by complete live-grid read-back), the Tauri backend (2026-09-14, the ignored backend smoke through the identity-carrying contracts), and the rendered Linux control (2026-09-15, a reversible real-unit DISTORTION edit driven through the running GUI's inspector and restored through the same control). The client checks identity immediately before mutation and again on read-back, but the device protocol has no atomic compare-and-write operation; a physical front-panel replacement inside that remaining interval therefore yields an unconfirmed write rather than a guaranteed atomic edit. Bypass is offline-verified only - it is exercised by Rust and browser-fixture tests, but no hardware pass has yet confirmed a GUI bypass toggle is audible and matches the unit's own display. Parameter search or grouping for models with many parameters is not yet implemented.

Nano Cortex surface

When the selected daemon owns a Nano Cortex, the GUI renders the fixed eight-role signal chain (Gate, Pre FX 1-2, Capture, IR/Cab, Post FX 1-3) as one ordered row of semantic block buttons, each showing its loaded model name and bypass state. The complete row fits without horizontal scrolling at the 1080x675 native minimum; at narrower effective widths such as high zoom it scrolls rather than inventing a wrapped topology. Capture and IR names come from device state; FX model and parameter names are resolved in cortex-rs from Nano-specific attributed tables, not guessed from the Quad catalog or implemented again in React. A model id added by newer firmware remains visible as Unknown model <id>, and an unknown parameter remains visible as Param <index>. Quad and Nano share the same visual information hierarchy, selected state and operational-state presentation, but the Nano's roles are never forced into the Quad's grid, scenes or presets. Every role can be selected with pointer or keyboard; editable FX roles expose normalized parameter controls, while the other roles explain that no editable parameters are available yet. Below the inspector, each of the five raw Amp controls (Gain, Level, Bass, Mid, Treble) combines a slider, editable 0-255 numeric input, explicit decrement/increment buttons and an Apply button over one draft value; the shared Linux Tauri command path is hardware-verified, and direct hardware coverage exercised all five values.

Each Apply sends the typed amp write to the daemon, which paces a fresh state read after the device's measured six-second settle and confirms the value read back exactly before returning. The initiating control stays focusable, reports aria-busy, and guards re-entry during that round-trip; the panel re-reads device state rather than displaying an optimistic value. The whole cycle changes heard working state and saves nothing.

Gate reduction shares the same slider-plus-numeric-stepper control as the Amp values, over a 0-100% range, with its own explicit Apply action. The Rust boundary rejects larger values and uses the same paced state read-back before reporting success. The integer core USB, held-daemon/MCP and Linux Tauri-backend paths were hardware-verified on 2026-09-04 with independent read-back and restoration; the official editor's 49.8% baseline decoded and restored as the protocol model's integer 50%. The rendered control interaction itself has not been separately exercised against hardware.

Gate/FX bypass is also exposed: a Switch control for each of the six addressable roles (Gate, Pre FX 1-2, Post FX 1-3) toggles bypass on or off. Like amp writes, each toggle sends the typed bypass write to the daemon, which paces a fresh state read after the device's measured six-second settle and confirms the new value before returning. The whole cycle changes heard working state and saves nothing.

Selecting an FX panel opens its parameter inspector. Editable panels are named button controls and support Enter/Space as well as pointer input; the header device selector is also a named native button rather than a pointer-only badge. Nano bypass controls include the slot and current state in their accessible names, Apply buttons identify the value they submit, and FX sliders identify their semantic name, explicit wire index and normalized range. The inspector first requests the model's normalized parameter values and only enables Apply once they are available. Each write carries the model id shown when those parameters were loaded; the daemon refreshes current state and rejects the write before device mutation if the player has since changed that model. Names are attributed host metadata; indices and normalized values are the hardware-verified wire facts, and the GUI does not invent units, ranges, enums or conversions that the reply does not carry. The underlying core and daemon/CLI paths were hardware-verified on 2026-08-21 across reads from all five slots, a reversible write/read-back/restoration cycle and a daemon-backed same-value write. The then-current Tauri backend and rendered native Linux control also passed all five reads and a reversible slider edit. NANO-001.9 subsequently changed the Tauri response to typed parameter descriptors and added a mandatory expected-model safety field; those current Tauri read/write contracts have only offline coverage until a new hardware smoke reaches the changed boundary. Amp, bypass and FX parameter edits change heard working state; no Nano operation saves a preset.

One decoder caveat: the Gate's "on" state is represented by the absence of field 54 in the state protobuf, so the decoder reports bypassed = None (unknown) when the gate is on rather than Some(false). The bypass toggle for the Gate is therefore disabled in the UI when the state reads as unknown; this is a decoder limitation, not a write failure, and the write itself still works.

The header device button identifies which product is selected and opens the selector for Quad Cortex, Nano Cortex, or Auto-detect. Quad and Nano use separate local endpoints, so both product sessions can remain live concurrently without giving either physical HID interface a second owner. Switching never shuts down the other product or replaces an explicitly started daemon. With both devices connected on 2026-08-25, a fresh app paid the Quad handshake once; Quad to Nano and the warm return to Quad then each rendered in under one second, compared with 5-8 seconds when every return rebuilt the Quad session.

Health, reconnect and fault isolation

The header badge shows whether the session is a daemon connection (its live state) or fixture mode. If the device is reconnecting or unavailable, the live panels hide rather than show stale data, and the reconnect panel offers attempt/error detail plus a manual Reconnect now control.

The scene selector, grid and inspector each render behind their own error boundary. If one panel's frontend code throws, that panel shows its own failure message and a reload control instead of silently disappearing while the other panels and the daemon connection keep working - which matters because a thrown error does not stop writes from reaching the device, only the panel that would show you they happened.