You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[1.7.0] - 2026-05-10 - Collaboration: PRs, Discord, LAN & WAN Live Sessions
Summary
Real-time multi-peer Live Sessions over WAN(Phase 9.8) - host an editing session, share a 4-character pairing code, and up to 8 joiners can co-edit a single MIDI file in real-time over WebRTC + DTLS, peer-to-peer after a one-shot Cloudflare-hosted rendezvous handshake. Joiner-initiated protocol (every joiner gets its own offer/answer pair), production-tested on 4 PCs at 1.8 KB/s host-out peak. Auto-reconnect on transport failure, ghost-peer protection via 10 s heartbeat / 30 s silence-deadline, and offline-edit recovery as a single state-diff on rejoin (COLLAB-WAN-001).
LAN Live Sessions(Phase 9.5) - same real-time co-editing over the local network with zero external dependencies. Multicast peer discovery, TCP transport, automatic full-file transfer to joiners on connect, and a manual-IP fallback for networks where multicast is blocked. Sessions show up in the Join LAN Live Session… dialog with one click (COLLAB-LAN-001).
Async PR workflow with smart-paste tokens(Phase 9.1) - Edit → Create PR… packages the current diff against a chosen parent into a self-contained token. Collaborators paste it into their editor with Ctrl+V and the Review PR… dialog opens with per-hunk cherry-pick. Identical to a code-review loop, no GitHub account needed, no server (COLLAB-PR-001).
Discord-channel notifications for PRs(Phase 9.2) - drop a webhook URL into Settings → Collaboration → Discord webhook; Create PR then posts a rich embed plus the smart-paste token to the channel, with a per-share Don't post this one opt-out for private one-offs (COLLAB-DISCORD-001).
State-diff sync, not an op-log - Live Sessions broadcast a hash-keyed snapshot diff every sync tick instead of streaming individual operations. Whatever a peer edited while disconnected lands as one catch-up diff on rejoin - no missed events, no replay-order conflicts. Free property: a forced network drop during edits round-trips cleanly to the host as soon as the channel reopens (COLLAB-DIFF-001).
Connection Test diagnostic - two-stage health probe (/health GET against the rendezvous URL + an in-process two-transport DTLS loopback) returns a quality grade in under six seconds. Surfaces the silent-firewall-block class of failures on Windows that previously looked like "the browser works but the app times out" (COLLAB-DIAG-001).
Returning-peer reconciliation - when a peer rejoins a session whose history they already have, MidiEditor offers a Fast-forward bundle of just the commits since the fork point instead of re-transferring the whole file. The new Welcome Back dialog summarises the divergence and lets the peer choose Fast-forward / Full reload / Cancel (COLLAB-FF-001).
New logging subsystem with dedicated Settings tab and manual page - five colour-coded severity levels (Critical / Warning / Info / Debug / Trace) selected via radio buttons with a live preview of exactly which log lines you'll see, a size-callout warning before enabling Debug or Trace, 10 MB file rotation with three numbered backups (40 MB total budget), per-category overrides for advanced troubleshooting, and an Open log file shortcut. The new manual/logging.html walks through every level with sample output and "when to attach a log to an issue" steps (LOGGING-001, DOC-LOGGING-001).
Lock side panels during playback - opt-in Settings → System & Performance → Playback toggle that restores the legacy "panels disabled during playback" behaviour for users who prefer fewer accidental edits while listening. Default off - panels stay fully interactive (PLAYBACK-LOCK-001).
Comprehensive Collaboration manual section - eight new manual pages cover the full workflow: a top-level Collaboration overview with mode-selector cards, plus dedicated PR / Discord / LAN / WAN / Settings / Cloudflare-self-host pages with synchronised SVG animations of each protocol's handshake, OBS recordings of two PCs editing simultaneously, and a Cloudflare-Worker walkthrough that goes from blank dashboard to deployed worker in a single screenshot strip (DOC-COLLAB-001).
Six new test executables - test_midi_diff, test_pr_bundle, test_drum_kit_preset, test_gp_binary_reader, test_mml_midi_writer, test_three_mle_parser add deterministic coverage for the collaboration diff pipeline, the Guitar Pro binary reader, the MML round-trip, and the 3MLE parser; the largest single-release expansion of the test harness so far (TEST-1.7-001).
Cloudflare rendezvous worker shipped under cloudflare/ - the small stateless KV-backed Worker that swaps SDP blobs between WAN peers ships in-tree with wrangler.toml, a deploy walkthrough in cloudflare/README.md, and v3 of the joiner-initiated multi-peer protocol baked in. Self-host it on the free tier in under five minutes if you don't want to use the shared Happy Tunes endpoint (CLOUDFLARE-001).
Full Changelog - Collaboration: PRs, Discord, LAN & WAN Live Sessions
New Features - Collaboration core
COLLAB-PR-001 - Asynchronous PR workflow with smart-paste tokens (Phase 9.1) - new top-level Edit → Create PR… and Edit → Review PR… commands plus a CollabService singleton that owns a per-file .midiedit-collab.json sidecar tracking the local commit history (parentHash chain, per-hunk MidiDiff payloads, knownPeers list). The PR token is a base64 + zlib-compressed self-contained bundle (PrBundle) carrying the parent SHA, a list of MidiDiff hunks, the author's display name + machine UUID, and an optional cover message. Pasting it triggers MainWindow::paste() to detect the prefix, open PrReviewDialog with a per-hunk cherry-pick checklist, apply selected hunks via PrApply inside one Protocol action (single Ctrl+Z undoes the whole import), and append the merged commit to the local history. The token format trims trailing/leading whitespace and surrounding triple/single backticks before parsing so Discord-pasted tokens "just work".
COLLAB-DISCORD-001 - Discord webhook integration for PR distribution (Phase 9.2) - new Settings → Collaboration → Discord webhook field plus an Also post to Discord checkbox in PrCreateDialog. When the URL is set and the checkbox is on, Create PR posts a Discord rich embed (file name, parent commit short-hash, author display name, hunk count) followed by a code-fenced smart-paste token via WebhookClient. A per-share Don't post this one opt-out is provided for private one-offs. Webhook URL is validated against the https://discord.com/api/webhooks/<id>/<token> schema; a missing or malformed URL silently disables the checkbox.
COLLAB-LAN-001 - LAN Live Sessions (Phase 9.5) - new Edit → Start LAN Live Session… and Edit → Join LAN Live Session… commands wire the host editor to LanLiveSession::startHosting() (multicast announcement on 239.255.42.99:45301, TCP listener on a random ephemeral port, full-file transfer to each joiner on connect) and the joining editor to a session-list dialog backed by LanDiscovery. Sync uses the same hash-keyed MidiSnapshot + MidiDiff pipeline as the PR workflow, broadcast every 1.5 s when the local snapshot hash changes. Joiners receive the host's file on connect, apply incoming hunks through PrApply inside per-message Protocol actions, and rebroadcast their own diffs to the host. Manual-IP fallback for blocked-multicast networks: hosts copy a lan://<ip>:<port> URL from the start dialog, joiners paste it into the join dialog. Mode is non-invasive - disabling collab via the master toggle in Settings hides every menu entry, and -DMIDIEDITOR_ENABLE_COLLAB=OFF produces a binary with zero Collab* symbols.
COLLAB-WAN-001 - Multi-peer WAN Live Sessions over WebRTC + Cloudflare rendezvous (Phase 9.8) - Phase 9.8's joiner-initiated protocol scales the original 1:1 WAN session to up to 8 joiners per host. Each joiner is the WebRTC initiator (creates its own offer SDP), the host is the responder (creates an answer per incoming offer), and the Cloudflare Worker holds an explicit joiner-index:CODE key plus per-joiner joiner-offer:CODE:<id> and host-answer:CODE:<id> slots. Host polls /joiner-offers every 2 s and processes new offers in arrival order. The WebRtcLiveServer owns one WebRtcTransport per joiner (QHash<QString, WebRtcTransport*>); each transport has its own DTLS credentials and SCTP data channel, so every peer's MIDI traffic is encrypted end-to-end and never traverses Cloudflare after the handshake. SDPs are bytewise tagged with the joinerId and DTLS fingerprints, so signalling forgery is structurally caught at handshake time. The host pre-flights /health against the rendezvous URL before publishing the session and aborts cleanly with a "rendezvous unreachable" message if it doesn't resolve. Peer count is capped at MAX_PEERS_PER_SESSION = 8 server-side; a full session returns HTTP 503 with a clear "session is full" message to the joiner. Production-tested 2026-05-08 on a 1-host + 3-joiner WAN session at 1.8 KB/s peak host-out - comfortable headroom on a 50 Mbit upstream.
COLLAB-DIFF-001 - MidiSnapshot + MidiDiff + PrApply state-diff pipeline - the live-sync engine doesn't stream MIDI events as they happen; it diffs the current snapshot against the last-broadcast snapshot every 1.5 s tick. MidiSnapshot::fromFile() extracts a sorted list of every event's identity tuple (channel, tick, type, key/control/program), MidiHash Blake2b's it for the wire-level dedup key, and MidiDiff::between(prev, next) produces a list of add / remove / modify hunks against tuple identity. PrApply::apply() re-finds matching events in the receiver's file by tuple (no fragile pointer or insertion-order tracking) and applies the hunks inside one Protocol::startNewAction block. Free property: any number of edits made while disconnected become exactly one catch-up diff on rejoin, no replay ordering needed and no possibility of double-application. Verified 2026-05-08: deleting notes on a joiner during a forced WAN drop propagated atomically to the host the moment LIVE returned.
COLLAB-FF-001 - Returning-peer reconciliation with fast-forward bundles (Phase 9.5g) - when a peer rejoins a session whose parentHash they already carry in their .midiedit-collab.json sidecar, HistoryReconciliation::commitsSinceFork() extracts just the commits since the divergence point and the host transfers a Fast-forward bundle (a list of PrBundle hunks, gzipped) instead of the full file. WelcomeBackDialog summarises the divergence (commits behind, last-known author, file-size delta) and lets the peer choose Fast-forward / Full reload / Cancel. Far-diverged peers (ancestorHash not found) fall back to a full file transfer with an inline "Unrelated histories - full reload required" notice.
COLLAB-DIAG-001 - Connection Test diagnostic - new Test connection button in Settings → Collaboration → WAN Rendezvous. Stage 1 issues a GET /health against the configured rendezvous URL and checks for a v3-protocol response. Stage 2 spins up an in-process two-WebRtcTransport DTLS loopback with host-only ICE candidates (no STUN), reports round-trip latency, and grades the result A/B/C. Run-time is bounded at six seconds total. Surfaces the silent firewall block on unsigned EXEs class of Windows failures that previously looked like "browser-OK + Qt-app-times-out" by reporting a stage-1 success and a stage-2 timeout.
COLLAB-IDENTITY-001 - Display name + machine UUID (Phase 9.1d) - new CollabIdentity::machineUuid() derives a stable installation ID once on first run from the system username + hostname + a QUuid::createUuid() salt, stored next to QSettings. Combined with the user-editable display name in Settings → Collaboration → Display name, this lets multiple PCs with the same display name still get distinct authorship in commit history without any registration flow. Used by every feature that emits an authored event: PrBundle author field, WebRtcStartDialog "host name" field, LanDiscovery announcement payload, History-tab commit attribution. A re-installed editor on the same machine keeps the same UUID so the user's commits stay attributed to them.
COLLAB-HISTORY-001 - Per-file collab history + History tab - new CollabHistoryWidget (always visible alongside the existing Tracks / Channels / Event / Protocol tabs when a sidecar exists) lists every commit in the local history with author, timestamp, hunk count, parent hash short-form, and a per-row "Use as parent for new PR" action. Sidecar is written through QSaveFile for atomic on-disk updates and tolerant field parsing for forward compatibility. History compaction is intentionally out of scope for v1.7; sidecars stay growable.
COLLAB-AUTORECONNECT-001 - Auto-reconnect on transport failure (WAN) - new Settings → Collaboration → Auto-reconnect on transport failure checkbox (default off) plus a Max retry attempts slider (1-5, default 2). When enabled, a Live session whose WebRTC channel dies restarts the rendezvous flow with the same code, ~2 s back-off between attempts. Each peer's reconnect is independent (one joiner's failed retry doesn't kick the others), and a sequence-counter check inside the retry lambda guarantees that pressing Leave between failure and the back-off cancels the pending restart cleanly. Caveat surfaced in the manual: won't recover when the host restarts and gets a new code - joiners enter the new code manually in that case.
COLLAB-WORK-ON-COPY-001 - Hosting safety: work-on-copy default - when Settings → Collaboration → Work on a copy when hosting is on (default), starting a Live session writes <name>_shared.mid into Documents/MidiEditor_AI/shared/ and switches the editor to that copy. The original file on disk is untouched until the user opts to save back manually. Non-MIDI source formats (Guitar Pro .gp3/.gp4/.gp5/..., MML, MusicXML, MuseScore - all import-only formats) automatically rewrite the shared-copy suffix to .mid so the re-open flow doesn't try to re-import MIDI bytes through the wrong importer; the original on disk stays in its native format.
New Features - Logging
LOGGING-001 - Logging subsystem with dedicated Settings tab and manual page - new LoggingConfig singleton owns a five-level severity ladder (Critical → Warning → Info → Debug → Trace), per-category Qt logging-rule overrides (midieditor.collab.*, midieditor.gui.*, midieditor.midi.*, midieditor.ai.*), and a 10 MB-rotated file logger (midieditor_ai.log + three numbered backups, 40 MB total disk budget). The Settings dialog grew a new Logging tab built around five colour-coded radio buttons (red Critical, orange Warning, blue Info, gray Debug, gray Trace) with a four-cell cumulative severity bar to the right of each row, a live preview text-block showing exactly which sample log lines you'll see at the selected level, and a size-callout box that turns yellow at Debug and red at Trace with an estimated MB/hour figure. Open log file opens the rotated log next to MidiEditorAI.exe in the OS file manager. Verbose collab logging checkbox is an overlay that promotes only the midieditor.collab.* categories to debug - handy for reproducing a session bug without flipping the global level. The Collaboration tab keeps a smaller mirror of the same controls so users debugging a collab issue don't have to switch tabs. New manual/logging.html walks through each level with sample output, size estimates per session-hour, and step-by-step "when to attach a log to an issue" guidance.
PLAYBACK-LOCK-001 - Lock side panels during playback (opt-in) - new Settings → System & Performance → Playback → Lock side panels during playback toggle (default off, key playback/lock_panels). When on, the Tracks / Channels / Event / Protocol tabs are disabled for the entire duration of playback and recording - restoring the legacy v1.4.1-and-earlier behaviour for users who prefer fewer accidental edits while listening. Default-off because the v1.4.2 redesign that made panels stay interactive during playback has been the documented behaviour for a year and is what users now expect.
Documentation
DOC-COLLAB-001 - Eight-page Collaboration manual section - new manual/collaboration.html overview with a fixed 2x2 mode-selector grid (PR + Discord on top, LAN + WAN below) and one-line "when to use" callouts; new manual/collab-pr.html walking through Create PR / Review PR with per-hunk cherry-pick screenshots; new manual/collab-discord.html linking to the official Discord webhook documentation plus the embed format we post; new manual/collab-lan.html with multicast vs manual-IP screenshots, an animated SVG of the LAN handshake (host announce → joiner discovery → TCP file-transfer → state-diff sync), troubleshooting copy for blocked-multicast networks, and OBS recordings of two PCs editing simultaneously; new manual/collab-wan.html with a matching two-track SVG animation of the WAN flow (orange Cloudflare handshake on top, green direct DTLS once the data channel opens), Connection Test screencap (collab_connection_test.webm), full troubleshooting section for the silent-firewall-block class of failures; new manual/collab-cloudflare.html self-host walkthrough that goes from blank Cloudflare dashboard → KV namespace bound → worker deployed in a single screenshot strip, with a 12 s SVG animation visualising every step of the v3 protocol synchronised against the step-list; new manual/collab-settings.html reference for every toggle on the Collaboration tab - Identity, Hosting safety, WAN Rendezvous, Discord webhook, Logging - including the Connection Test video and per-setting Default / When to change notes. The manual sidebar nav (navigation.js) gained a Collaboration group in the Editor section so all eight pages are one click apart.
DOC-LOGGING-001 - Dedicated logging manual page - new manual/logging.html covers the five severity levels with sample log lines per level (colour-coded <span class="lvl-CRI/WRN/INF/DBG/TRC">), the cumulative level-stack model, green/yellow/red size-callout boxes with estimated MB/hour at each level, the rotation policy (10 MB cap × 3 backups = 40 MB total), per-category overrides for advanced users, and a "when to attach a log to an issue" checklist. Linked from the new Logging Settings tab via Read the dedicated Logging manual page and from the Collaboration overview via Verbose collab logging.
DOC-PLAYBACK-001 - Playback page refresh - manual/playback.html gained a Live Side Panels During Playback section documenting the opt-in lock toggle (PLAYBACK-LOCK-001) with a screenshot of the new Settings → System & Performance → Playback panel, and the previous HTML4 align="right" image floats were converted to centered block-level images so the page renders cleanly on narrow viewports.
DOC-FOOTER-001 - Manual footer alignment - manual/site.css now centers the GitHub-repo logo + text combo in the .footer-links flex row with align-items: center; so the icon-bearing link doesn't drop below the baseline of the text-only links on every manual page.
Test Harness
TEST-1.7-001 - Six new test executables - tests/test_midi_diff.cpp (snapshot diff round-trip, hash equality, modify-vs-add-vs-remove classification, track-field round-trip across all 10 event types), tests/test_pr_bundle.cpp (token build + parse, prefix detection, base64 + zlib round-trip, malformed token rejection, whitespace-stripping pre-parse), tests/test_drum_kit_preset.cpp (FFXIV percussion preset coverage map, edge-case GM keys), tests/test_gp_binary_reader.cpp (GP3/GP4/GP5 chunk parse, byte-order, fixture-driven note recovery), tests/test_mml_midi_writer.cpp (MML round-trip including dotted notes and ties), tests/test_three_mle_parser.cpp (3MLE channel header parse + chord-stack lowering). All six wired into tests/CMakeLists.txt with the WIN32 PATH-injection block so Qt6Test.dll resolves under ctest.
Cloudflare Worker
CLOUDFLARE-001 - Rendezvous Worker shipped in-tree under cloudflare/ - cloudflare/rendezvous.js (v3 protocol, joiner-initiated multi-peer, explicit joiner-index:CODE key for KV list()-consistency, soft MAX_PEERS_PER_SESSION = 8 cap, 5-minute TTL), cloudflare/wrangler.toml (KV binding scaffolding), and cloudflare/README.md (web-UI deploy walkthrough + CLI deploy via wrangler + free-tier capacity table). The default rendezvous URL in the editor points at the shared Happy Tunes endpoint https://midi-rdv.happytunesai.workers.dev/; users who don't want to share that endpoint can self-host on the Cloudflare free tier in under five minutes following the in-tree README. SDPs contain public IP + port info; the worker never logs them, and they expire after five minutes.
Architecture / Technical Notes
-DMIDIEDITOR_ENABLE_COLLAB=OFF clean opt-out - the entire collab subsystem is gated behind a single CMake option and a single master toggle in Settings. Building with the option off produces a binary with zero references to any Collab*, Lan*, WebRtc*, or Pr* symbol; every menu entry, settings tab, and dialog disappears at compile time. This is a non-invasive guarantee per 09_COLLABORATION §4 so users uninterested in collab pay no binary-size, no startup-time, and no attack-surface cost.
Single Protocol action per incoming hunk batch - PrApply::apply() always runs inside one Protocol::startNewAction("Live edit from <author>") block, so a single Ctrl+Z undoes a remote peer's whole sync tick atomically. Same for the "fast-forward bundle on rejoin" path: an entire catch-up diff unwinds with one undo.
Cloudflare KV list() eventual-consistency workaround - KV's list() operation has unbounded eventual-consistency lag (observed ~14+ s before a freshly put()'d key appears in list() results from the same PoP). The v3 protocol routes around this by maintaining an explicit joiner-index:CODE key written + read with get() / put() (read-your-writes consistent within a single PoP), eliminating the 30+ second startup delay we observed in real WAN sessions on v2.
Files Added - Collaboration core (src/collab/)
CollabService.{h,cpp} - singleton owning the per-file sidecar, commit history, and the active LiveSession handle.
CollabIdentity.{h,cpp} - stable display-name + machine-UUID derivation, stored next to QSettings.
CollabHistoryFile.{h,cpp} - atomic .midiedit-collab.json reader/writer using QSaveFile, tolerant field parsing.
MidiHash.{h,cpp} - Blake2b digest of a MidiSnapshot for the wire-level dedup key.
MidiDiff.{h,cpp} - diff (prev, next) snapshots into add / remove / modify hunks with stable identity tuples.
PrApply.{h,cpp} - apply a hunk list to a target file inside one Protocol action; defensive trackIdx = 0 fallback for older-build hunks missing the track field.
LanDiscovery.{h,cpp} - multicast announce + listen on 239.255.42.99:45301 with multi-interface IPv4 handling.
LanTransport.{h,cpp} - TCP transport for LAN sessions with size cap and per-iteration null guards.
LanLiveSession.{h,cpp} - host/joiner state machine, snapshot-tick broadcaster, heartbeat timer (10 s ping / 30 s deadline), and machineId-based ghost-peer dedup on the hello handshake.
manual/screenshots/collab_connection_test.webm, live_LAN_pc1.webm, live_LAN_pc2.webm, live_WAN_pc_1.webm, live_WAN_pc2.webm - OBS-recorded clips embedded in the LAN, WAN, and Settings pages.
Files Added - Tests
tests/test_midi_diff.cpp, tests/test_pr_bundle.cpp, tests/test_drum_kit_preset.cpp, tests/test_gp_binary_reader.cpp, tests/test_mml_midi_writer.cpp, tests/test_three_mle_parser.cpp - see Test Harness above for coverage detail.
CMakeLists.txt - version bumped to 1.7.0; MIDIEDITOR_ENABLE_COLLAB build option added with the corresponding target_compile_definitions gates; new test executables registered.
src/main.cpp - LoggingConfig::initialize() and category rule application happen before any QObject is constructed so startup is logged at the user-selected level.
src/midi/MidiFile.{h,cpp} - exposes the immutable iteration views needed by MidiSnapshot::fromFile() and emits a fileChanged() signal that LanLiveSession uses to drive the snapshot-tick poll.
src/ai/MidiEventSerializer.{h,cpp} - every event-type serializer (note, cc, pitch_bend, program_change, tempo, time_sig, key_sig, text, chan_pressure, key_pressure) now consistently round-trips the track field with a null-pointer guard, so MidiDiff matches events across builds correctly and the receiver-side PrApply can route hunks to the correct track.
src/gui/MainWindow.{h,cpp} - top-level menu wiring for Edit → Create PR… / Review PR… / Start LAN Live Session… / Join LAN Live Session… / Start WAN Live Session… / Join WAN Live Session…; paste() now detects smart-paste tokens via PrBundle::looksLikeToken (whitespace-trimmed and backtick-stripped) and routes them through PrReviewDialog; prepareHostFile() writes the work-on-copy with the correct .mid suffix when the source is import-only; MidiPilotWidget integration unchanged for non-collab users.
src/gui/MatrixWidget.{h,cpp} - paint-path additions for the optional remote-cursor overlay used by Live Sessions (collab-only - invisible outside an active session).
src/gui/MidiPilotWidget.{h,cpp} - minor wiring to coexist with the Collaboration sidebar tab.
src/gui/SettingsDialog.cpp - registers the new Collaboration and Logging tabs; System & Performance gained the Lock side panels during playback checkbox.
manual/playback.html - added Live Side Panels During Playback lock-toggle section; converted HTML4 align="right" image floats to centered block-level images so the dialog screenshot no longer overflows into the footer.
manual/navigation.js - Collaboration group inserted into the Editor section of docGroups.
manual/site.css - .footer-links gained align-items: center; so the GitHub-repo icon sits on the same baseline as text-only links across every manual page.
manual/docs-index.html - new Collaboration card linking to collaboration.html plus a Logging card linking to logging.html.
tests/CMakeLists.txt - registered the six new test executables with the WIN32 PATH-injection block.