Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
568dcd1
feat(plugins): let plugins declare button actions via buttonActions API
xal3xhx Apr 13, 2026
5a4cee6
feat: add --devtools flag to open devtools in release builds
xal3xhx Apr 13, 2026
c7240b0
fix(ui): preserve plugin action selection labels and clean OBS target…
prgmitchell Apr 13, 2026
df7595e
merge: PR #13 plugin button actions and OBS target UX fixes
prgmitchell Apr 13, 2026
d8eeb01
fix: auto-detect relative MIDI deltas and harden focus volume fallback
prgmitchell Apr 14, 2026
5aae275
chore: bump version to 1.2.0
prgmitchell Apr 14, 2026
ca070b1
chore: sync Cargo.lock for 1.2.0
prgmitchell Apr 14, 2026
70f5f31
fix(ui): disable default context menu on right click
prgmitchell Apr 14, 2026
f41f975
feat(hotkeys): add hotkey target learn flow and trigger support
prgmitchell Apr 14, 2026
18b219f
chore(release): bump version to 1.3.0
prgmitchell Apr 14, 2026
4acc61f
feat(bindings): add set default device action for audio targets
prgmitchell Apr 14, 2026
5a9a4f9
chore(release): bump version to 1.4.0
prgmitchell Apr 14, 2026
440a467
feat(open-application): add target picker flow with persisted app icon
prgmitchell Apr 15, 2026
bad2daa
chore(release): bump version to 1.5.0
prgmitchell Apr 15, 2026
c9e9dcf
feat: add built-in updater via GitHub releases
prgmitchell Apr 15, 2026
270e540
chore(release): bump version to 1.6.0
prgmitchell Apr 15, 2026
5bedf71
feat(updater): refine startup update prompt and settings UX
prgmitchell Apr 15, 2026
aa2167c
chore(release): bump version to 1.7.0
prgmitchell Apr 15, 2026
b5f0106
fix(audio/windows): prevent master 0% writes from leaving endpoint muted
prgmitchell Apr 16, 2026
b90d51b
fix(ui): prevent settings overflow at small window heights
prgmitchell Apr 16, 2026
0a89784
chore(release): bump version to 1.8.0
prgmitchell Apr 16, 2026
5fd6e19
chore(store): trust rotated key and ignore local store tooling
prgmitchell Apr 17, 2026
fbee053
feat(tray): open app on left-click and show menu on right-click
prgmitchell Apr 17, 2026
c2d682e
chore(release): bump version to 1.9.0
prgmitchell Apr 17, 2026
4d893ad
fix(ci): format store_api and enforce pre-push rustfmt check
prgmitchell Apr 17, 2026
fa928dc
feat(plugins): add ctx.osd namespace for plugin-driven OSD control
xal3xhx Apr 17, 2026
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: 8 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -eu

echo "[pre-push] Running Rust format check..."
cd src-tauri
cargo fmt --all -- --check

echo "[pre-push] Format check passed."
59 changes: 55 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,61 @@ jobs:
- name: Install Windows bundler tools
shell: pwsh
run: |
choco install nsis wixtoolset --no-progress -y
choco install nsis --no-progress -y

- name: Validate updater signing secrets
shell: pwsh
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if ([string]::IsNullOrWhiteSpace("${env:TAURI_SIGNING_PRIVATE_KEY}")) {
throw "Missing TAURI_SIGNING_PRIVATE_KEY secret. Configure updater signing secrets before releasing."
}

- name: Build (Tauri)
working-directory: src-tauri
run: cargo tauri build --bundles nsis,msi
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: cargo tauri build --bundles nsis

- name: Generate updater metadata (latest.json)
shell: pwsh
run: |
$tag = "${env:GITHUB_REF_NAME}"
$repo = "${env:GITHUB_REPOSITORY}"

$nsisDir = "src-tauri/target/release/bundle/nsis"
$setupExe = Get-ChildItem -Path $nsisDir -Filter *.exe |
Where-Object { $_.Name -like "*setup*.exe" } |
Select-Object -First 1

if (-not $setupExe) {
throw "Could not find NSIS setup executable in $nsisDir"
}

$sigPath = "$($setupExe.FullName).sig"
if (-not (Test-Path $sigPath)) {
throw "Missing signature file for updater artifact: $sigPath"
}

$signature = (Get-Content -Raw $sigPath).Trim()
$downloadUrl = "https://github.com/$repo/releases/download/$tag/$($setupExe.Name)"

$latest = @{
version = $tag
notes = "See release notes on GitHub."
pub_date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
platforms = @{
"windows-x86_64" = @{
signature = $signature
url = $downloadUrl
}
}
}

$latestJsonPath = "src-tauri/target/release/bundle/latest.json"
$latest | ConvertTo-Json -Depth 8 | Out-File -FilePath $latestJsonPath -Encoding utf8

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
Expand All @@ -59,5 +109,6 @@ jobs:
fail_on_unmatched_files: true
files: |
src-tauri/target/release/midimaster.exe
src-tauri/target/release/bundle/**/*.exe
src-tauri/target/release/bundle/**/*.msi
src-tauri/target/release/bundle/nsis/*.exe
src-tauri/target/release/bundle/nsis/*.exe.sig
src-tauri/target/release/bundle/latest.json
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,16 @@ target/

# Tauri
src-tauri/gen/

# Netlify local metadata
.netlify/
netlify.toml

# Local store/operator workflow (keep private, local-only)
plugin-staging/
scripts/store/
scripts/local-store/
docs/STORE_OPERATOR_GUIDE.md
src-tauri/src/bin/store_tool.rs
store-private-key*.txt
*.midimaster.key
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,30 @@ cargo tauri dev

Releases are created from git tags.

One-time updater key setup (required for built-in updates):

1. Generate a signing keypair locally:
```powershell
cargo tauri signer generate -w "$HOME/.tauri/midimaster.key"
```
2. Copy the generated public key into `src-tauri/tauri.conf.json` at `plugins.updater.pubkey`.
3. Add these GitHub repo secrets for the release workflow:
- `TAURI_SIGNING_PRIVATE_KEY` (contents of the private key file, or a secure path)
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` (empty string if no password)

Release flow:

1. Update `src-tauri/Cargo.toml` version.
2. Create and push a tag in the form `v<version>` (example: `v0.1.0`).

Pushing the tag triggers the GitHub Actions release workflow, which builds the Windows bundle and
attaches the installer artifacts to a GitHub Release.
publishes NSIS installer artifacts, signatures, and `latest.json` to GitHub Releases.

Built-in updater behavior:

- Update checks use GitHub Releases only (`releases/latest/download/latest.json`).
- Stable releases only are offered.
- If in-app update fails, users can still install manually from the GitHub release page.

Documentation

Expand Down
73 changes: 69 additions & 4 deletions docs/PLUGIN_DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,72 @@ Use `silent: true` for:
- Reconnect sync
- Motor fader alignment

### 6.6 `ctx.ws` (WebSocket bridge)
### 6.6 `ctx.osd` (On-screen display)

Direct control of the OSD overlay for plugin-driven feedback that is not
tied to a specific binding (e.g. a status update, an external event echo,
a notification triggered from a connection tab).

If the feedback *is* tied to a binding, prefer `ctx.feedback.set(...)` —
it updates the UI, OSD, and motor faders in one call. Use `ctx.osd.*`
only when you need to show an OSD card without touching binding state.

API:

- `await ctx.osd.showVolume(target, volume, opts?)`
- `await ctx.osd.showMute(target, muted, opts?)`
- `await ctx.osd.hide()`
- `await ctx.osd.getSettings()` -> `{ enabled, monitor_index, monitor_name, monitor_id, anchor }`
- `ctx.osd.onSettingsChanged(handler)` -> unsubscribe fn

`target` is the same shape used in bindings — e.g. `"Master"`,
`{ Session: { name: "Discord" } }`, or an integration target like
`{ Integration: { integration_id: "my-plugin", target_id: "room/living" } }`.

`opts`:

- `focusSession` — override the label/icon used for `Focus` targets.
- `showBar` (default `true`) — set `false` to hide the volume bar, e.g. for
text-only status messages where the bar is meaningless.
- `showValue` (default `true`) — set `false` to hide the percent/icon on the
right side of the card.

Integration targets bound to bindings skip the default OSD card, so a plugin
owns the OSD for its own targets. Pair `ctx.osd.showVolume(...)` with
`ctx.feedback.set(binding_id, value, action, { silent: true })` to update
the controller/UI without emitting a duplicate OSD card.

To keep repeated calls reusing the same card (rather than stacking a new
card per message), put dynamic text in `data.label`. The OSD key-builder
strips `label` when computing the target key, so calls with the same
`integration_id` + `kind` + remaining `data` collapse onto one card.

```js
await ctx.osd.showVolume({ Integration: { integration_id: "my-plugin", target_id: "zone/a" } }, 0.65);
await ctx.osd.showMute("Master", true);
await ctx.osd.hide();

// Text-only banner (no bar, no value) reusing a single card.
const textTarget = (msg) => ({
Integration: { integration_id: "my-plugin", kind: "status", data: { label: msg } },
});
await ctx.osd.showVolume(textTarget("Connected"), 0, { showBar: false, showValue: false });

const settings = await ctx.osd.getSettings();
if (!settings.enabled) {
// OSD window is disabled — user-facing cues should use another channel
}

const off = ctx.osd.onSettingsChanged((next) => {
console.log("OSD anchor is now", next.anchor);
});
// off(); when the plugin unloads
```

OSD visibility still respects the user's global OSD toggle. Calls from a
plugin while the OSD is disabled are silently dropped.

### 6.7 `ctx.ws` (WebSocket bridge)

Use this when you need custom headers or consistent backend-managed sockets.

Expand All @@ -406,7 +471,7 @@ Message handler receives:
- `{ id, type: "text", data: string }`
- `{ id, type: "binary", data: base64String }`

### 6.7 `ctx.assets` (Read plugin assets)
### 6.8 `ctx.assets` (Read plugin assets)

- `await ctx.assets.readBase64(relPath)` -> base64 string
- `await ctx.assets.readDataUrl(relPath, mime)` -> `data:<mime>;base64,...`
Expand All @@ -417,7 +482,7 @@ Example:
const icon = await ctx.assets.readDataUrl("icon.svg", "image/svg+xml");
```

### 6.8 `ctx.tauri` (Low-level)
### 6.9 `ctx.tauri` (Low-level)

Advanced escape hatch:

Expand All @@ -426,7 +491,7 @@ Advanced escape hatch:

Prefer stable APIs (`ws`, `feedback`, etc.) when possible.

### 6.9 `ctx.app.invalidateBindingsUI()`
### 6.10 `ctx.app.invalidateBindingsUI()`

If your plugin's connection/availability state changes, call:

Expand Down
Loading
Loading