teamsiso/CHANGELOG.md

341 lines
20 KiB
Markdown
Raw Permalink Normal View History

# Changelog
All notable changes to TeamsISO are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
chore(docs): reconcile to WPF-only after WinUI 3 was abandoned - Fix TeamsISO.Windows.slnf — drop the dangling src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj entry whose project doesn't exist in the .sln (broke the build on main). - Archive the abandoned WinUI 3 artifacts under docs/archive/: * 2026-05-12-winui3-migration.md (the nine-phase migration plan) * TeamsISO.App.WinUI.Probe/ (the bootstrap diagnostic console) * work-log-2026-05-12-winui3.md (the overnight session log) - README — drop the "in-flight WinUI 3 replatform" status block; state that the v2 redesign landed in WPF and link the shape brief. Keyboard shortcuts table picks up Ctrl+K, Ctrl+T, and the digit hotkeys that already shipped. - CHANGELOG — replace the WinUI-3-flavoured "Ground-up GUI redesign" block with a v2 Studio Terminal entry that names Task 39 + Task 40 as landed. De-dupe the May 2026 batch: the second "Quick-join Teams meeting from URL", "IN-CALL bar surfaces Teams meeting state", and "Auto-launch Teams + auto-hide windows" bullets were verbatim repeats of earlier entries; kept the first occurrence. - NEXT_STEPS.md — rewrite to reflect that Task 39 (participants table v2) and Task 40 (Ctrl+K palette) both shipped; v1.0 cut is now gated only on MSI signing + real-meeting smoke pass. - DESIGN.md — small WPF-isms: WinUI 3 composition layer → WPF's; Segoe Fluent Icons phrased without the "WinUI 3's bundled" qualifier; migration boundary rephrased to "rewrites MainWindow.xaml + Themes/*" instead of "everything in Views/". - .gitignore — ignore the .claude/ session metadata dir so it doesn't show up as untracked on every dev checkout. Build + tests verified before commit: 0 errors, 0 warnings; 160 tests pass (56 App + 104 Engine, filter Category!=ndi&requires!=ndi). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:16:20 -04:00
### Added — v2 "Studio Terminal" GUI (2026-05-13)
chore(docs): reconcile to WPF-only after WinUI 3 was abandoned - Fix TeamsISO.Windows.slnf — drop the dangling src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj entry whose project doesn't exist in the .sln (broke the build on main). - Archive the abandoned WinUI 3 artifacts under docs/archive/: * 2026-05-12-winui3-migration.md (the nine-phase migration plan) * TeamsISO.App.WinUI.Probe/ (the bootstrap diagnostic console) * work-log-2026-05-12-winui3.md (the overnight session log) - README — drop the "in-flight WinUI 3 replatform" status block; state that the v2 redesign landed in WPF and link the shape brief. Keyboard shortcuts table picks up Ctrl+K, Ctrl+T, and the digit hotkeys that already shipped. - CHANGELOG — replace the WinUI-3-flavoured "Ground-up GUI redesign" block with a v2 Studio Terminal entry that names Task 39 + Task 40 as landed. De-dupe the May 2026 batch: the second "Quick-join Teams meeting from URL", "IN-CALL bar surfaces Teams meeting state", and "Auto-launch Teams + auto-hide windows" bullets were verbatim repeats of earlier entries; kept the first occurrence. - NEXT_STEPS.md — rewrite to reflect that Task 39 (participants table v2) and Task 40 (Ctrl+K palette) both shipped; v1.0 cut is now gated only on MSI signing + real-meeting smoke pass. - DESIGN.md — small WPF-isms: WinUI 3 composition layer → WPF's; Segoe Fluent Icons phrased without the "WinUI 3's bundled" qualifier; migration boundary rephrased to "rewrites MainWindow.xaml + Themes/*" instead of "everything in Views/". - .gitignore — ignore the .claude/ session metadata dir so it doesn't show up as untracked on every dev checkout. Build + tests verified before commit: 0 errors, 0 warnings; 160 tests pass (56 App + 104 Engine, filter Category!=ndi&requires!=ndi). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:16:20 -04:00
The May 2026 ground-up redesign — explicit anti-reference to "the v1
GUI screamed AI made it" — landed on the WPF host
(`src/TeamsISO.App/`). The shape brief lives at
`docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md`. An earlier WinUI
3 replatform was scoped on 2026-05-12 and abandoned in favour of doing
the redesign in WPF (activation blockers + redundant work given the
shared view-model surface). The abandoned migration plan + bootstrap
probe are archived under `docs/archive/`.
- **PRODUCT.md** + **DESIGN.md**: impeccable context for the redesign.
Solo broadcast operator at 1:50am is the canonical persona; "vibe-coded
GUI" is the explicit anti-reference. Tokens cover dark + light palettes
with context-aware accent split (cyan surface fill stays bright in
chore(docs): reconcile to WPF-only after WinUI 3 was abandoned - Fix TeamsISO.Windows.slnf — drop the dangling src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj entry whose project doesn't exist in the .sln (broke the build on main). - Archive the abandoned WinUI 3 artifacts under docs/archive/: * 2026-05-12-winui3-migration.md (the nine-phase migration plan) * TeamsISO.App.WinUI.Probe/ (the bootstrap diagnostic console) * work-log-2026-05-12-winui3.md (the overnight session log) - README — drop the "in-flight WinUI 3 replatform" status block; state that the v2 redesign landed in WPF and link the shape brief. Keyboard shortcuts table picks up Ctrl+K, Ctrl+T, and the digit hotkeys that already shipped. - CHANGELOG — replace the WinUI-3-flavoured "Ground-up GUI redesign" block with a v2 Studio Terminal entry that names Task 39 + Task 40 as landed. De-dupe the May 2026 batch: the second "Quick-join Teams meeting from URL", "IN-CALL bar surfaces Teams meeting state", and "Auto-launch Teams + auto-hide windows" bullets were verbatim repeats of earlier entries; kept the first occurrence. - NEXT_STEPS.md — rewrite to reflect that Task 39 (participants table v2) and Task 40 (Ctrl+K palette) both shipped; v1.0 cut is now gated only on MSI signing + real-meeting smoke pass. - DESIGN.md — small WPF-isms: WinUI 3 composition layer → WPF's; Segoe Fluent Icons phrased without the "WinUI 3's bundled" qualifier; migration boundary rephrased to "rewrites MainWindow.xaml + Themes/*" instead of "everything in Views/". - .gitignore — ignore the .claude/ session metadata dir so it doesn't show up as untracked on every dev checkout. Build + tests verified before commit: 0 errors, 0 warnings; 160 tests pass (56 App + 104 Engine, filter Category!=ndi&requires!=ndi). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:16:20 -04:00
both modes; cyan-as-text darkens to `#0E7C82` on light for AA contrast).
- **Theme system** (`Themes/Theme.Dark.xaml`, `Theme.Light.xaml`,
`WildDragonTheme.xaml`) + `Services/ThemeManager.cs` singleton that
swaps the merged dictionary at runtime, reads
`HKCU\…\AppsUseLightTheme` for System mode, subscribes to
`SystemEvents.UserPreferenceChanged`, persists via
`UIPreferences.Theme`. `Ctrl+T` toggles dark ↔ light.
- **v2 main window shell**: default system title bar; 32px header (Wild
Dragon mark + wordmark left, ⌘K / theme / settings icons right); 40px
transport strip (`● 02:14:32 PART 4 · LIVE 2 CTRL :9755`); body with
alert banner + update banner + action toolbar + participants
DataGrid; conditional `IN CALL` meeting bar at bottom; slide-over
settings drawer (420px from right) with OUTPUT / NETWORK / APP tabs.
The v1 72px rail, the 380px permanent settings panel, and the
six-column footer are gone.
- **Task 39 — participants table v2**: five columns (24px state LED,
name + codec caption, 110px audio meter, 130px mono output name, 100px
ISO pill), 52px rows, full-row active-speaker tint (replaces the v1
left-edge stripe).
- **Task 40 — Ctrl+K command palette**: `Views/CommandPaletteWindow.xaml`
+ `ViewModels/CommandPaletteViewModel.cs`. Centered 560×360 floating
window with fuzzy search across Quick / Teams / Presets / Output /
Network / App categories. ↑/↓ navigates, Enter invokes, Esc closes.
- **Interactive HTML preview** at `docs/preview/redesigned-mainwindow.html`
for stakeholders to see the v2 shell.
### Added — May 2026 feature batch
#### Engine
- NDI Groups: discovery + sender support so Teams' raw broadcasts can be
pinned to a private "teamsiso-input" group while TeamsISO's own
normalized outputs broadcast on Public.
- One-click "Apply transcoder topology" writes `ndi-config.v1.json` so all
Teams broadcasts go to the private group and TeamsISO re-emits on Public.
- `RawBgraRecorderSink` per-output recorder: `IRecorderSink` interface +
raw BGRA stream + `manifest.json` + `convert.cmd` script for FFmpeg
conversion to H.264 MKV.
- Recording markers: `IRecorderSink.AddMarker(label)` fan-out via
`IIsoController.AddRecordingMarker`. Markers land in `manifest.json`
under `markers[]` for post-production chaptering.
- Preview thumbnails: `IsoPipeline.LatestProcessedFrame` published via
`Volatile.Read` so the UI can render 160×90 BGRA thumbnails in the
participants DataGrid at 1Hz.
- Idempotent `ParticipantTracker.HandleAdded`: re-emitting Added for an
already-live source refreshes LastSeen instead of duplicating the row.
Fixes the "click Refresh and rows ghost-duplicate" bug introduced by
the Refresh-Discovery affordance.
- `IIsoController.RefreshDiscovery()` rebuilds the NDI finder on the next
poll tick — useful right after applying a new transcoder topology.
- `IIsoController.AddRecordingMarker(label)` fan-out to every active recorder.
- `IIsoController.SetRecording(enabled, dir)` global recording toggle;
per-participant override via `EnableIsoAsync(...recordOverride...)`.
#### Host (WPF)
- Active Speaker as a synthetic routable participant with deterministic v5
GUID derived from `auto-mix:<machine>`.
- Auto-disable on departure: when a participant's NDI source disappears,
optionally tear down their pipeline.
- Operator presets: chromeless `Presets…` dialog with Save / Apply /
Delete / Duplicate / Export / Import. Persisted at
`%LOCALAPPDATA%\TeamsISO\presets.json`. Bundle format
`teamsiso-presets-bundle/v1` for migration between machines.
- Auto-apply last preset on launch (configurable, off by default).
- `--apply-preset NAME` CLI flag for desktop-shortcut workflows.
- `PresetApplier` — single source of truth for "apply preset to live
participants" used by the dialog, REST surface, and auto-apply path.
- Live preview thumbnails per participant (160×90 BGRA WriteableBitmap).
- Right-click context menu on participant rows: Toggle ISO, Record-this-
participant, Copy NDI source name.
- Live filter input (substring match on display name).
- "Enable all online" + "Stop all ISOs" + "Refresh" header actions.
- Per-participant recording opt-out checkbox (Rec column).
- Custom NDI output name template with `{name}`/`{guid}`/`{machine}`/
`{timestamp}` tokens.
- Phase E.1 — Launcher: rail "Launch / Stop Teams" toggle.
- Phase E.2 — Window orchestration: hide / show Teams windows from the rail.
- Phase E.3 — In-call controls (UIA): Mute, Camera, Share, Leave, Raise hand,
plus PostMessage shortcut forwarding fallback. Candidate names localized
for English / German / Spanish / French / Portuguese / Japanese.
- Crash diagnostics: AppDomain + Dispatcher + TaskScheduler unhandled
exception handlers wired to Serilog.Critical + user-facing dialog.
- First-launch onboarding dialog with 5-step setup checklist.
- About dialog gained "Show welcome", "Check for updates", "Export diagnostics"
buttons.
- Diagnostic bundle export: zips logs + config + presets + version metadata
into `~/Downloads/teamsiso-diagnostics-<ts>.zip` for bug reports.
- Update check: manual via About + auto-on-launch banner (throttled to 24h,
opt-out via flag file at `%LOCALAPPDATA%\TeamsISO\no-update-check.flag`).
- Disk space watcher auto-disables recording at <1GB free.
- Settings panel refactored into OUTPUT / NETWORK / DISPLAY tabs.
- Reset-to-defaults button in OUTPUT tab.
- Enriched footer: REC badge, control-surface badge, session timer (HH:MM:SS
since first ISO went live), dynamic status text ("3/5 ISOs live · 2 recording").
- Window-scoped keyboard shortcuts: F1 (help), Ctrl+M (marker), Ctrl+Shift+S
(stop all), Ctrl+R (refresh discovery).
- F1 help / cheat-sheet dialog.
- `UIPreferences` static persists `HideLocalSelf`, `AutoDisableOnDeparture`,
`ParticipantSort` (JoinOrder / Alphabetical / OnlineFirst) across launches
to `%LOCALAPPDATA%\TeamsISO\ui-prefs.json`.
- Pop-out per-participant preview window (right-click → Open preview…)
refreshes at ~20Hz and is multi-monitor friendly.
- Configurable participant sort order via the DISPLAY tab dropdown.
- Stop-All confirms before tearing down running pipelines (catches
mid-show misclicks).
- About dialog gained "Logs / Recordings / Notes" folder shortcut buttons.
- `NotesWindow` inline viewer for today's show-notes file with 2s polling.
- Duplicate-preset action in the Presets dialog with smart `(copy N)`
name suggestions.
- `--apply-preset NAME` command-line flag for desktop-shortcut workflows.
- New `TeamsISO.App.Tests` net8.0-windows test project. Initial coverage:
`OperatorPresetStoreTests` (round-trip, name collisions, schema, bundle
import/export, garbage-file resilience), `OutputNameTemplateTests` (token
expansion + sanitization), `OscMessageTests` (wire-format parsing of
int/float/string/T/F type tags). Backed by an `InternalsVisibleTo` grant
+ a test-only `OperatorPresetStore.PathOverride` hook.
- `IsoHealthStats.PeakAudioLevel` field + DataGrid VU-bar UI scaffolding.
Engine still emits 0.0 (audio capture is a focused follow-up); the bar's
decay logic is in place so it animates as soon as engine-side audio
parsing lands.
- `MediaFoundationRecorderSink` scaffold under `#if MF_AVAILABLE` for
inline H.264 encoding via Vortice.MediaFoundation. ~10× smaller files
than the raw BGRA recorder. Activation steps documented at
`docs/REAL-TIME-RECORDING.md`.
- System-tray icon + minimize-to-tray toggle. Adds
`<UseWindowsForms>true</UseWindowsForms>` for `NotifyIcon`; the
`TrayIconHost` lives on `App` (process lifetime, not main-window
lifetime). Right-click menu has Show / Stop all ISOs / Exit.
- Built-in NDI test pattern: `TeamsISO.Console --test-pattern` broadcasts
a synthetic 1280×720 30fps source named `TEAMSISO_TEST` showing SMPTE
color bars + a moving sweep band. Verifies NDI runtime, sender
configuration, and downstream discovery without needing Teams running.
Backed by `TestPatternGenerator` in the engine + 4 unit tests covering
buffer size, alpha, color distinctness, and sweep animation.
- Always-toast on participant disconnect, regardless of `AutoDisableOnDeparture`
setting. Distinguishes "ISO torn down" (auto-disable on) from "ISO still
running on slate" (auto-disable off) so operators don't miss a silent
drop mid-show.
- **Restart this ISO** right-click action — disable + brief delay + re-enable
for one participant only. Useful when a single feed flakes without
affecting other ISOs.
- **Roll recording** action: rolls every active recording into a new chunk
(disable + re-enable each pipeline; recorder finalizes its `manifest.json`
and starts a fresh subdirectory). Surfaced via `MainViewModel.RollRecordingCommand`,
REST `POST /recording/roll`, and OSC `/teamsiso/recording/roll`. Useful
for chaptering between show segments.
- **Engine audio peak metering** — `IsoHealthStats.PeakAudioLevel` now
reports real values (was always 0.0). New `INdiInterop.CaptureAudioPeak`
method polls audio frames; production `NdiInteropPInvoke` parses
`NDIlib_audio_frame_v3_t` and computes max-absolute peak across all
channels. `NdiReceiver` runs a sibling audio capture loop on the same
lifetime so the existing video path is unaffected. UI VU bars in the
participants DataGrid now animate against real source audio. Failures
in the audio loop are caught + logged but never re-thrown — a
misbehaving audio path must never tear down the live video pipeline.
14 new unit tests in `AudioPeakComputerTests` covering FLTP / FLT / PCM
s16 across edge cases (clipping, `short.MinValue` overflow, defensive
`totalSamples`-vs-buffer mismatch handling).
#### LAN-reachable control surface
- `ControlSurfaceServer.Start(port, bindToLan)` and `OscBridge.Start(port,
bindToLan)` switch between `127.0.0.1` and all-interfaces (`http://+:port/`,
`IPAddress.Any`) based on the new `ControlSurfaceLanReachable` UI preference.
Settings VM persists the toggle, restarts both surfaces on flip, and
surfaces a `ControlSurfaceUrl` (computed from the host's first physical-NIC
routable IPv4 — Tailscale / VPN / APIPA addresses are skipped) plus a Copy
button. Use case: headless host PC running Teams + TeamsISO; thin client
on the same LAN drives `/ui` or hits the REST endpoints. Closed-network
deployment, no auth — documented as a trusted-LAN-only mode in
`docs/CONTROL-SURFACE.md`. First-time use requires a one-shot
`netsh http add urlacl url=http://+:9755/ user=Everyone` (so the listener
can bind without admin); the diagnostic warning fires the exact command
string in the log if the bind fails.
#### "I only see TeamsISO" — Phase E.1+E.2 quality-of-life
For operators who want to launch TeamsISO and never look at the Teams UI:
- **Launch Microsoft Teams on TeamsISO startup** preference (DISPLAY tab).
Auto-fires Teams in the background each time TeamsISO starts; the Teams
window appears briefly during boot then can be hidden automatically.
- **Auto-hide Teams windows when launched** preference (DISPLAY tab).
`TeamsLauncher.AutoHideAfterLaunchAsync` polls for 15s after launch
hiding splash, main window, and follow-up panels as they materialize.
Works on top of the existing eye-toggle for manual restore.
- **Quick-join Teams meeting from URL** — small input + Join button in the
IN-CALL bar. Paste a `https://teams.microsoft.com/l/meetup-join/...`
or `msteams:/l/meetup-join/...` link, click Join, Teams launches into
the meeting in one shot. Eliminates the open-Teams → Calendar → find →
click Join dance. Pairs with auto-hide so the operator goes straight
from "I have a meeting link" to "I'm in the meeting, driving routing".
- **Teams meeting state pill** in the IN-CALL bar — shows `IN CALL · <meeting title>`
/ `READY` / empty. UIA-driven probe of Teams' Leave button at 1Hz; the
meeting title comes from Teams' window title with the brand suffix
stripped. So an operator with auto-hide on knows whether they're in a
meeting AND which one without restoring the Teams window. 10 new unit
tests on `MainViewModel.ExtractMeetingTitle`.
- **Rail Launch Teams click semantics** — was ambushing operators with a
"Close all Teams windows now?" dialog whenever Teams was running (e.g.
when hidden via the eye-toggle). Now click = launch / surface / restore;
right-click = stop. `TeamsLauncher.TryLaunch` now collects per-attempt
errors (no more silent fall-through) and adds the AppX-activation
fallback for hosts where the `ms-teams:` URI handler is misconfigured.
- **Auto-record when Teams joins a meeting** preference. Recording auto-
flips ON when Teams transitions into a call (UIA Leave button appears)
and auto-flips OFF when the call ends. Removes the manual Record toggle
step from unattended-show workflows.
- **Phase E.4 (experimental) — SetParent embedding.** Reparents Teams' main
window into a TeamsISO-owned host (`TeamsEmbedWindow`) so Teams appears
visually INSIDE TeamsISO. Strips Teams' window chrome and resizes to
fit. Modern Teams runs WebView2 in its main window which can render
glitches after reparent; if so the operator unticks and falls back to
auto-hide mode. `TeamsLauncher.EmbedTeamsInto` / `RestoreEmbed` /
`ResizeEmbedded` form the lifecycle. Restore-on-close runs in a finally
block so a crash can't leave Teams orphaned with stripped window styles.
- **Right-click → Save current frame** on a participant row. Encodes the
latest `ProcessedFrame` as a PNG under
`%USERPROFILE%\Pictures\TeamsISO\<participant>_<timestamp>.png`.
Useful for highlight reels, social posts, bug reports.
- **Open /ui button** in Settings → DISPLAY → Control surface section.
Fires the URL into the default browser for one-click preview of the
embedded control panel.
- **Recording badge in footer shows elapsed duration** alongside the count
(`REC 3 · 12:45`). Separate timer from the session timer because
recording can start AFTER the meeting begins.
- **MUTED / CAM OFF pills** in the IN-CALL bar — UIA detects whether the
local user is muted or has their camera off, surfaces as coral pills.
Operator with auto-hide knows the local state without restoring Teams.
- **Recording drive free space** in the footer (`· 245 GB free`). Coral
tint below 10GB; existing DiskSpaceWatcher still auto-disables at 1GB.
- **Loudest sort mode** for the participants DataGrid + **active speaker
row highlight** (3px cyan left border + tinted background) on whoever's
speaking. Operators react to who's talking without scanning every VU bar.
- **Snapshot all enabled participants** — header action saves every
enabled participant's current frame as a PNG into a fresh timestamped
subfolder under `%USERPROFILE%\Pictures\TeamsISO\snapshots-<ts>\`.
- **NumPad 1-9 (and Digit 1-9) hotkeys** toggle the Nth visible
participant's ISO. Sort + filter aware — index matches what's on screen.
Generic `RelayCommand<T>` added to ViewModels/RelayCommand.cs so XAML
CommandParameter strings convert to the action's T.
#### UI polish — visible affordances on the dark canvas
- Hover state on every themed button (Ghost / Caption / RailIcon / IsoToggle)
was barely distinguishable from the resting state. Bumped `Wd.SurfaceHover`
+ `Wd.SurfaceActive` colours for sufficient contrast, added dedicated
`Wd.Button.HoverBg` / `Wd.Button.PressBg` brushes with a slight chroma tint,
and added cyan accent borders so mouse-hover and tab-focus give an
unmistakable affordance regardless of which surface the button sits on.
IsoToggle keeps its status-coded background (LIVE cyan / ERROR coral /
NO SIGNAL amber) on hover; the affordance is a 2px cyan border pop.
- `IsKeyboardFocused` triggers added on every themed button so tab-cycling
through the UI gives visual feedback (was nothing — `FocusVisualStyle`
was `x:Null` with no replacement).
- ScrollBar restyled: slim transparent track + tinted thumb (Edge / VS Code
pattern) in place of the chunky Win9x default with line-up / line-down
arrow buttons. Track-clicks above/below the thumb still page-scroll.
- ToolTip restyled: SurfaceElevated card with rounded 6px corner + 320px
text wrap, replacing the cream Win98 popup. Affects every tooltip across
MainWindow.
- ContextMenu / MenuItem restyled: dark card with rounded corners + cyan-
tinted hover. Affects the right-click menu on participant rows
(Toggle ISO / Restart this ISO / Open preview / Record / Copy NDI source name).
- CheckBox content no longer clips at the 380px settings panel: template's
StackPanel replaced with a Grid (Auto + *) and a TextWrapping=Wrap
resource injected into the ContentPresenter so long labels flow onto
multiple lines.
- Manual X dismiss on toast notifications for live-show situations where
the operator wants to clear visual clutter without waiting 3s.
- Footer's control-surface badge surfaces the full LAN URL (not just port)
when LAN-reachable mode is on, so a thin client can be configured by
reading the URL straight off the host's footer.
#### Control surface
- REST API on `127.0.0.1:9755` with endpoints for participant ISO toggle (by
Id or display name), preset apply, refresh discovery, stop-all, recording
on/off, marker drop, notes, and Teams in-call commands. Documented at
`docs/CONTROL-SURFACE.md`.
- WebSocket `/ws` pushes live participant state at 4Hz with snapshot diffing.
- OSC bridge on UDP `127.0.0.1:9000` mirrors the REST vocabulary
(`/teamsiso/iso "Jane" 1`, `/teamsiso/preset "..."`, etc.).
- Embedded HTML control panel at `GET /ui` — phone-friendly remote with
live state and one-click action buttons.
- Show notes service: `POST /notes` and `/teamsiso/notes "..."` append
timestamped lines to `%LOCALAPPDATA%\TeamsISO\Notes\<YYYY-MM-DD>.md`.
#### CI / Release
- Forgejo CI is green; tag-push release workflow builds + tests + publishes
+ builds MSI on a Windows runner and attaches it to the auto-created
release via the REST API.
- Optional MSI + exe code-signing wired into `release.yml` — gated on
`SIGN_CERT_PFX_BASE64` + `SIGN_CERT_PASSWORD` Forgejo Secrets.
### Fixed
- `.slnf` path-separator mismatch (forward slashes for cross-platform).
- NDI native DLL resolution via `NativeLibrary` resolver.
- `ExpectedRuntimeVersionPrefix` updated to NDI 6 banner format.
- `NdiSourceParser` accepts current Teams desktop's `MS Teams - <name>`
brand format.
- ActiveSpeaker source removal no longer poisons the rename-window
heuristic for a Participant joining the same machine within the window.
- `IsoPipeline.State` access synchronized via `Volatile.Read/Write`.
- REST handlers now correctly marshal `ObservableCollection` reads + writes
through the UI dispatcher.
- WebSocket upgrade no longer falls into `res.Close()` finally block (was
killing freshly-upgraded connections).
- `ParticipantViewModel.UpdateThumbnail` defends against malformed frames
(`width*height*4 > Pixels.Length`).
- `HasThumbnail` correctly fires `PropertyChanged` when `Thumbnail`
transitions from null.
- WinForms / WPF `Application` and `MessageBox` namespace collision
(introduced when `<UseWindowsForms>true</UseWindowsForms>` was added for
the system tray) resolved via project-wide `GlobalUsings.cs`.
- `GetLanIPv4()` now skips Tailscale / VPN tunnel adapters and APIPA
(`169.254.x`) so the displayed control-surface URL points at the
routable LAN IP (verified on a host with both Ethernet 10.x and a
Tailscale 169.254 link-local — picker now correctly returns the
Ethernet address).
[Unreleased]: https://forge.wilddragon.net/zgaetano/teamsiso/compare/v0.1.0...HEAD