Conversation
Phase 2 — Migrate EmoTracker.Core and EmoTracker.Data to net8.0 - EmoTracker.Core.csproj: TargetFramework net472 → net8.0 - EmoTracker.Data.csproj: TargetFramework net472 → net8.0; remove redundant System.IO.Compression reference (built-in to .NET 8) - DotNetFrameworkVersion.cs: replace Windows-registry-dependent body with a no-op stub (net8.0 has no .NET Framework version to check) NOTE: EmoTracker and EmoTracker.UI still target net472. Phase 4 will add multi-targeting (net8.0-windows) to those projects to reunify the build. Phase 3 — Decouple WPF types from extension interface and services 3.1 Extension interface (Extension.cs) - StatusBarControl: FrameworkElement → object so the interface has no UI-framework dependency; implementations return the platform control 3.2 Replace Application.Current.Dispatcher with Dispatch.BeginInvoke - AutoTrackerExtension, MemorySegment, MultiWorldClientSession, MultiWorldExtension, TwitchExtension, ApplicationModel: all direct Dispatcher.BeginInvoke calls replaced with Core.Services.Dispatch.BeginInvoke 3.3 Replace DispatcherTimer with System.Timers.Timer - AutoTrackerExtension.cs: mUpdateTimer converted to System.Timers.Timer - ApplicationModel.cs: package-refresh timer and notification-expiry timer both converted to System.Timers.Timer 3.4 Introduce IDialogService and IWindowService abstractions - New interfaces: Services/IDialogService.cs, Services/IWindowService.cs - WPF implementations: Services/DialogService.cs (WpfDialogService), Services/WindowService.cs (WpfWindowService — cross-platform OpenFolder and OpenUrl using explorer/open/xdg-open) - ApplicationModel.cs: all MessageBox.Show, Microsoft.Win32 file dialogs, Keyboard.Focus, Application.Current.MainWindow.Width/Height, and Process.Start for folder/URL replaced with service calls - ApplicationModel.cs no longer imports System.Windows.Input or Microsoft.Win32 dialog types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EmoTracker.UI.csproj - TargetFrameworks: net472 → net8.0-windows;net8.0 - UseWpf conditioned on net8.0-windows - Markdig.Wpf conditioned on net8.0-windows - Avalonia 11.2.7 + Avalonia.Xaml.Behaviors + Markdown.Avalonia 11.0.2 added under net8.0 (source populated by Phase 5) - All existing WPF source excluded from net8.0 build (placeholder until Phase 5 adds Avalonia replacements) EmoTracker.csproj - TargetFrameworks: net472 → net8.0-windows;net8.0 - UseWpf conditioned on net8.0-windows; OutputType=Library for net8.0 - ApplicationManifest and AutoGenerateBindingRedirects conditioned on Windows - WINDOWS define constant set for net8.0-windows - ConnectorLib, PresentationFramework.Aero, NDI project reference all conditioned on net8.0-windows - System.Speech and System.ComponentModel.Composition migrated from bare <Reference> to NuGet PackageReference (8.0.0 / 4.7.0) - Markdig.Wpf and WpfScreenHelper conditioned on net8.0-windows - CopyNativeDependencies and GenerateInstaller build targets conditioned on net8.0-windows - All WPF-dependent and Windows-only source files excluded from net8.0 via conditioned <Compile Remove> items Notification.cs - Removed unused System.Windows and System.Windows.Threading usings so Notification and MarkdownNotification compile on net8.0 Properties/AssemblyInfo.cs - Removed top-level System.Windows using - ThemeInfo assembly attribute wrapped with #if WINDOWS (WPF-only type) ApplicationModel.cs - Fixed stray closing paren left from earlier Dispatcher→Dispatch migration (})); → });) in PushMarkdownNotification Services/WindowService.cs - Added missing using System; (needed for OperatingSystem.IsWindows()) Build result: net8.0-windows (WPF) and net8.0 (Avalonia placeholder) both compile clean with 0 errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All controls, converters, and the image pipeline in EmoTracker.UI now compile for both the WPF (net8.0-windows) and Avalonia (net8.0) targets using #if WINDOWS conditional compilation. Key changes: - EmoTracker.UI.csproj: Add WINDOWS define, Avalonia/Markdig/SkiaSharp packages, explicit WPF-only file exclusions (MarkdownViewer.xaml.cs, Settings.Designer.cs) - IconUtility: Avalonia path uses SkiaSharp for pixel ops (color key, grayscale, brightness, saturation, overlay compositing); alpha masks cached for hit testing - ImageReferenceService + all 3 resolvers: return IImage (Avalonia) or ImageSource (WPF) - InputMaskingImage: Avalonia version uses pointer event filtering + precomputed alpha mask from IconUtility; WPF version unchanged - ObservableUserControl, MouseOnlyButton/ToggleButton: namespace swaps - All 9 converters: System.Windows.Data → Avalonia.Data.Converters; type-specific changes for ThicknessConverter (Avalonia.Thickness) and InverseTransformConverter (Matrix.TryInvert) - MarkdownToFlowDocumentConverter: WPF-only (#if WINDOWS) - MarkdownProcessor.AsHtml: shared; AsFlowDocument: WPF-only - MarkdownViewer.cs: new Avalonia code-only implementation using Markdown.Avalonia.MarkdownScrollViewer + StyledProperty Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…les clean) - Add Avalonia 11.2.7 packages and Program.cs entry point for net8.0 - Add App.axaml / App.axaml.cs using OnFrameworkInitializationCompleted - Add MainWindow.axaml / MainWindow.axaml.cs (Avalonia Window, custom chrome) - Port all 16 UI AXAML controls: TrackableItemControl, LayoutControl, LocationControl, LocationMapControl, ChestListControl, CapturableItemControl, NoteTakingIconPopup, NoteTakingSiteView, MarkdownTextNoteControl, OverrideExportDialog, PackageManagerWindow, DeveloperConsole, AppUpdateWindow (stub), GroupedLocationListControl, ItemGridControl, TwitchStatusIndicator, VariantSwitcherControl - Update DispatchService / DialogService / WindowService with #if WINDOWS guards - Guard ApplicationModel ShowDialog calls and ListCollectionView for net8.0 - Add NullToFalseConverter, NonZeroToBoolConverter, BoolInverseConverter, InverseBoolConverter to EmoTracker.UI.Converters - Fix LayoutControl.axaml: DataTemplates→UserControl.DataTemplates, GroupBox→Border - Fix AppUpdateWindow.axaml: remove EmoTracker.Update namespace reference - Fix CapturableItemControl.axaml: StrokeDashArray comma syntax, PlacementMode Both net8.0 and net8.0-windows targets now build with 0 errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 7 — ApplicationModel async dialogs: - Add async method variants to IDialogService (ShowYesNoCancelAsync, ShowYesNoAsync, ShowOKAsync, OpenFileAsync, SaveFileAsync) - Add AvaloniaDialogService using MsBox.Avalonia 3.0.0-rc2 for message boxes and Avalonia StorageProvider for file pickers - WpfDialogService gains async impls wrapping sync via Task.FromResult - ApplicationModel: convert 6 command handlers to async void, using await on dialog calls (InstallPackage, UninstallPackage, RefreshHandler, ResetUserDataHandler, OpenHandler, SaveAsHandler) - Add MsBox.Avalonia 3.0.0-rc2 package reference for net8.0 target Phase 8 — Publish profiles: - Add Properties/PublishProfiles/ with four publish profiles: win-x64 (net8.0-windows, self-contained, single-file) osx-x64, osx-arm64, linux-x64 (net8.0, self-contained, single-file) - Usage: dotnet publish /p:PublishProfile=<name> Both net8.0 and net8.0-windows targets build with 0 errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndows Targets net8.0 (Avalonia) with win-x64 RID — same OS as the WPF build but exercises the cross-platform code path without Windows-only extensions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…esome
- Register pack: URI scheme in Program.cs so PackageManager/LocationDatabase
field initializers (new Uri("pack://application:,,,/...")) don't throw
UriFormatException on .NET 8
- Add avares:// and pack:// translation support to IconUtility.GetImage(Uri)
so embedded resources are loaded via Avalonia AssetLoader
- Add <AvaloniaResource> items to EmoTracker.csproj for net8.0 target so
icons and Resources/** are embedded as Avalonia assets (not WPF <Resource>)
- Define FontAwesome5Free/FontAwesome5Brands FontFamily resources in App.axaml
to fix InvalidCastException from unresolved StaticResource keys
- Fix MainWindow.axaml icon source to use avares:// URI
- Add public parameterless constructor to AppUpdateWindow (AVLN3001)
- Replace deprecated PlacementMode= with Placement= in two Popup declarations
- Suppress NU1701 for WebSocketSharp (no net8.0 target, compat shim is fine)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NoPackagePlaceholder was always visible (no IsVisible binding), covering the tracker content — add IsVisible binding via NullToTrueConverter on Tracker.Instance.ActiveGamePackage - LayoutControl DataContext binding used RelativeSource on MainWindow.ActiveLayout but the shadowed INotifyPropertyChanged event broke change notifications — replace with direct TrackerLayout.DataContext assignment in RefreshTrackerLayout() - Window dragging not implemented — add TitleBar_PointerPressed handler that calls BeginMoveDrag(e) on left-click of non-Button areas - ExtendClientAreaToDecorationsHint caused Avalonia to inset content by the OS title bar height, clipping the custom chrome buttons — remove the two extend client area attributes; SystemDecorations="BorderOnly" provides the resize border - Change Window Background from debug magenta (#ff00ff) to #111111 - Add NullToTrueConverter to VisibilityConverters.cs (returns true when null) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LayoutControl.axaml:
- Add ItemContainerTheme to DockPanel template so DockPanel.Dock is
forwarded from each item's DockLocation string via StringToDockConverter
- Add ItemContainerTheme to CanvasPanel template so Canvas.Left/Top/ZIndex
are forwarded from each item's CanvasX/Y/Depth via CanvasPositionConverter
and CanvasZIndexConverter
- Add Width/Height (NegativeToNaN) and IsHitTestVisible bindings to all
DataTemplate Grid wrappers, matching WPF LayoutItemStyle behaviour
- Move GroupBox DataTemplate before Container: Avalonia uses first-match
inheritance lookup, so the more-derived type must be declared first
- Rewrite GroupBox DataTemplate as two-row Grid (header bar + content area)
with HeaderBackground/Background colour bindings, matching WPF LayoutGroupBox
LocationMapControl.axaml:
- Add Background binding to map-location Border using new
AccessibilityLevelToBrushConverter so accessibility colours are shown
- Add IsVisible="{Binding Location.HasVisibleSections}" to location Grid
VisibilityConverters.cs:
- Add StringToDockConverter (string → Dock, case-insensitive)
- Add NegativeToNaNDoubleConverter (−1 → NaN for Width/Height)
- Add CanvasPositionConverter (≤0 → 0.0 for Canvas.Left/Top)
- Add CanvasZIndexConverter (≤0 → 0 for Canvas.ZIndex)
- Add StringToBrushConverter (colour name/hex string → IBrush)
- Add AccessibilityLevelToBrushConverter (AccessibilityLevel → IBrush
from ApplicationColors.Instance)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e manager, right-click - Add DoubleToThicknessConverter; use it in LocationMapControl for map marker BorderThickness (fixes invisible outer border on map location squares) - Add Background binding to ArrayPanel/DockPanel/CanvasPanel/Container/ScrollPanel DataTemplate Grids in LayoutControl.axaml (fixes wrong/missing backgrounds on panels) - Add Padding="0" to all 6 title bar chrome buttons in MainWindow.axaml (fixes icon clipping in the 25px title bar row) - Add PackageGroup class + AvailablePackagesGroupedView property in ApplicationModel; update PackageManagerWindow.axaml to bind to it instead of AvailablePackagesView.Groups (fixes package manager showing no packages in Avalonia build) - Remove Button.ContextMenu from TrackableItemControl; add Grid_PointerReleased handler that executes mRegressCmd on right-click directly (fixes empty context menu instead of right-click action) - Fix ArrayPanel DataTemplate to respect Orientation (vertical/horizontal) via StackPanel bound to DataContext.Orientation through RelativeSource on ItemsControl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SKBitmap.Decode() may return Rgb888x (no alpha channel) for RGB PNG or 24-bit BMP sources. For that color type, SetPixel(Transparent) is a silent no-op — the alpha byte stays forced to 255 — so magenta color-key pixels became opaque black instead of transparent, and the SkToAvalonia alpha mask was all-true (hit testing broken). Fix: promote the decoded bitmap to Bgra8888 before the color-key loop in GetImage(). Apply the same promotion in ToSkBitmap() so ApplyOverlayImage always composites with a real alpha channel (otherwise Max(base.Alpha, overlay.Alpha) would be 255 everywhere and transparent regions in stacked/layered item images couldn't be preserved). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve alpha loss Avalonia's Bitmap.Save(Stream) may strip the alpha channel on some platforms. ToSkBitmap used it to convert IImage back to SKBitmap for overlay blending. When alpha was stripped, every pixel appeared fully opaque (alpha=255), making the overlay formula treat the entire overlay as a solid mask — the base image was completely obliterated and only the last layer of stacked items was visible. Fix: SkToAvalonia now always stores the raw Skia-encoded PNG bytes in sPngCache alongside the alpha mask. ToSkBitmap reads from that cache first, bypassing Bitmap.Save entirely. The Bitmap.Save path is kept as a fallback only for images that did not originate from SkToAvalonia (e.g. GetImageRaw file/avares bitmaps). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mages - LocationMapControl: replace IsLightDismissEnabled with manual popup management; use DoubleTapped event for pin (not PointerPressed ClickCount); set LocationControl DataContext imperatively to bypass OverlayLayer ElementName binding limitation; add badge hover popup - LayoutControl: move RecentPinnedLocations DataTemplate before ArrayPanel so the derived type's template wins Avalonia's first-match lookup - LocationControl: add compact-mode layout (SectionsItemsPanel, SectionHorizontalAlignment, SectionItemMargin computed properties); bind ChestListControl.Compact; use AccessibilityLevelToBrushConverter for section name foreground; prevent width stretch in pinned panel - ChestListControl: pre-compute per-slot images in ObservableCollection instead of WPF MultiDataTrigger; add CurrentCompactImage StyledProperty; cache all display-relevant StyledProperty values in fields so UpdateChests never calls GetValue() during visual tree teardown; only trigger UpdateChests for the 7 display-relevant properties to avoid crash when Avalonia fires inherited property changes on popup overlay close - IconUtility: fix alpha channel loss (SKAlphaType.Opaque → Premul via SKCanvas copy); add async HTTP image loading with ConcurrentDictionary cache and HttpImageLoaded event - ApplicationModel: subscribe to HttpImageLoaded with debounced refresh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PushPinCheckBox ControlTheme: FontAwesome pushpin () with
:not(:checked) rotation (90°) and :disabled visibility, replacing the
plain CheckBox that showed Fluent theme's default checkmark
- Fix TrackableItemControl (GateItem/HostedItem) IsVisible bindings: change
from {Binding GateItem/HostedItem, Converter=NullToFalse} to
{Binding Converter=NullToFalse} — the DataContext is already set to the
item on the same element, so the path was resolving against the item
itself (not the parent Section), silently failing and always showing true
- Add TitleText computed property to LocationControl: returns ShortName
when Compact=true, Name otherwise; notifies on CompactProperty and
DataContextProperty changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the drop shadow feature for layout elements using Avalonia's DropShadowDirectionEffect (BlurRadius=15, ShadowDepth=0, Opacity=0.8), matching the centred-glow appearance of the WPF DropShadowEffect. Adds BoolToDropShadowEffectConverter and wires Effect binding to all outer Grid containers in LayoutControl.axaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a DropShadowDirectionEffect (BlurRadius=15, ShadowDepth=0, Opacity=0.8) directly on the LocationControl inside the map's LocationDetails popup, matching the WPF version's glow appearance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avalonia's TypeConverter (ThicknessTypeConverter) is only used during
XAML literal parsing, not at binding resolution time. A {Binding Margin}
where the source is a plain string therefore silently falls back to
Thickness(0). Add StringToThicknessConverter (Avalonia-only) and wire
it into every Margin binding in LayoutControl.axaml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WPF LayoutItemStyle conditionally applied all four size-constraint properties via DataTrigger. The Avalonia version only bound Width and Height, silently dropping every min/max constraint from layout JSON. A MaxHeight omission is the direct cause of the pinned-locations panel growing unboundedly instead of capping at its configured size. Missing MinWidth/MaxWidth constraints also explain inconsistent resize behaviour. Adds NegativeToZeroDoubleConverter (MinWidth/MinHeight, -1 → 0) and NegativeToInfinityDoubleConverter (MaxWidth/MaxHeight, -1 → ∞) to mirror the WPF DataTrigger guard, and wires all four into every outer Grid wrapper in LayoutControl.axaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ignores *.user, *.suo, .vs/, and .claude/settings.local.json which are developer-machine-specific and should not be tracked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…back MainWindow.axaml: - Replace RenderTransform on TrackerScaleGrid with LayoutTransformControl. RenderTransform is post-layout only so Ctrl+scroll zoom left dark background visible (scale < 1) or clipped content (scale > 1). LayoutTransformControl participates in the layout pass like WPF's LayoutTransform. MainWindow.axaml.cs: - Add UpdateResizeMode() to mirror WPF's AllowResize=false behaviour: sets SizeToContent=WidthAndHeight and CanResize=false so the window shrinks to the pack's natural content size (same as WPF SizeToContent trigger). - Subscribe to Tracker.PropertyChanged so AllowResize changes at runtime update the window's resize mode (e.g. when switching packs). - Fix RefreshTrackerLayout() to fall back to Width/Height when Bounds is 0×0. At construction time Bounds has not been measured yet; using 0>0=false always picked horizontal layout regardless of the configured window size. LayoutControl.axaml: - Add HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" to the root ContentControl so the DataTemplate-generated layout always fills the full content area rather than relying on Avalonia's Left/Top defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the LayoutItem.Scale / OverrideScale feature in Avalonia, equivalent to WPF's LayoutItemStyle DataTrigger that applied a LayoutTransform when OverrideScale=true. - Add LayoutItem.EffectiveScale (returns Scale when OverrideScale, else 1.0) - Wrap every LayoutControl DataTemplate's content in LayoutTransformControl bound to EffectiveScale so the per-element scale participates in the layout pass (Margin/HAlign/VAlign moved to LTC; Width/Height/Min/Max remain on the inner Grid, matching WPF ContentPresenter semantics) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iners In WPF, ContentPresenter.HorizontalContentAlignment defaults to Stretch, so DataTemplate content fills its item container and layout constraints flow correctly (DockPanel LastChildFill, Viewbox scaling, etc.). In Avalonia it defaults to Left, causing each layout element to be arranged at its desired/natural size — the map panel dictated layout size instead of filling the remaining window space. Fix: add a shared StretchItemContainer ControlTheme and apply it as ItemContainerTheme on every layout-hosting ItemsControl (ArrayPanel, DockPanel, CanvasPanel, ViewBox, GroupBox, Container, ScrollPanel). DockPanel and CanvasPanel already had inline themes for attached properties; Stretch setters are merged into those. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default ItemsControl panel is a vertical StackPanel, which measures children with infinite height. This breaks DockPanel.LastChildFill downstream — the map gets infinite remaining space and measures at its natural image size rather than filling the window's available space. Container (JSON types "container"/"grid") maps to a single-cell Grid in WPF. Switching the ItemsPanel to Grid passes finite height constraints through the layout chain, so the DockPanel receives the actual window content height and the map's Viewbox scales to fit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ItemsControl used the default vertical StackPanel, ignoring the pack JSON orientation/style settings. Pinned location cards stacked vertically instead of flowing horizontally with wrapping. Fix: use WrapPanel with Orientation bound to the model's Orientation property (parsed from the pack's "orientation"/"style" fields). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add StringToThicknessConverter to ItemGridControl margin bindings (Avalonia doesn't auto-convert string→Thickness in bindings) - Apply NegativeToNaNDoubleConverter to Item DataTemplate IconWidth/IconHeight - Implement full ButtonPopup DataTemplate with gear icon, image, and popup - Add HeaderContent support to GroupBox DataTemplate header bar - Add PreserveDimension binding to RecentPinnedLocations LocationControls - Add ComputedMaxWidth/MaxHeight constraints to LocationControl - Move PreserveDimension enum to EmoTracker.UI for cross-project access - Add ObjectEqualsConverter and OrientationToPreserveDimensionConverter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bindings from inside ItemsPanelTemplate to ancestor controls don't reliably resolve in Avalonia. Walk the visual tree to find the named MapsPanel StackPanel and set its Orientation imperatively on load, aspect ratio change, and DataContext change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ment WPF defaults HorizontalAlignment to Stretch, allowing LocationControls to fill the WrapPanel column width uniformly. The explicit Left alignment in the Avalonia template prevented this, causing uneven column widths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move Popup outside LayoutTransformControl (matching WPF structure) so popup content is not scaled by the button's EffectiveScale. Add IconDimensionMultiConverter that falls back to the source bitmap's pixel dimensions when IconWidth/IconHeight are unspecified (-1/NaN), instead of using NaN auto-sizing which interacts badly with parent layout transforms. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- F2: Implement BroadcastView window for Avalonia and wire up ShowBroadcastView command (was a no-op with #if WINDOWS guard) - F11: Add MapLocationVisibilityConverter that replicates WPF's MultiDataTrigger logic for hiding cleared/empty locations based on DisplayAllLocations, shift key, ForceVisible/ForceInvisible - Use tunnel routing for KeyDown to match WPF's PreviewKeyDown behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nd building Pack load for Codetracker Map Tracker was taking ~7.8s. Three optimizations: 1. Dictionary index for GetPersistableLocationReference/ItemReference: Replace O(n) List.IndexOf with O(1) dictionary lookup. LocationDatabase and ItemDatabase now maintain a Dictionary<T, int> populated on insert. Saves ~2.9s from 583 location lookups during voice command building. 2. Defer BuildCommandMap to background thread: Voice recognition command map building (~5s) now runs on Task.Run with snapshots of item/location data. Atomic swap under lock ensures thread safety. Cancellable on pack switch. Completely removes voice recognition from the critical path. 3. Code-to-provider index in ItemDatabase: New BuildCodeIndex() creates a Dictionary<string, List<ITrackableItem>> mapping codes to their providers. LuaItems (dynamic Lua callbacks) fall back to brute-force iteration. All item types implement GetAllProvidedCodes() to expose their static code sets. ProviderCountForCode and related methods use the index when available, avoiding iteration over all items for each code query. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… exception handling The developer console (ScriptManager) is intended for pack developers to debug their Lua scripts and pack definitions. Infrastructure subsystem messages from Voice Recognition, NDI, and MCP were leaking into it via the Serilog DeveloperConsoleSink, confusing users with errors unrelated to their packs (e.g. PortAudio enumeration failures, NDI init warnings). Changes: - DeveloperConsoleSink now filters out log messages prefixed with [Voice], [NDI], or [MCP]. These still go to the file log and stdout. - NdiSendContainer.OnAttachedToVisualTree wrapped in try-catch so NDI init/runtime errors are caught and logged rather than propagating. - NdiSendContainer.Dispose NDI cleanup wrapped in try-catch to prevent exceptions during teardown from surfacing. Closes #37 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Optimize pack load performance (~7.8s to <1s)
…t, and popup light-dismiss TrackableItemControl now uses a standard Image (rectangular hit test) by default and only uses InputMaskingImage (per-pixel alpha) when MaskInput is true. IgnoreUserInput is enforced via IsHitTestVisible binding, CanExecute checks, and an early guard in Grid_PointerReleased. LocationMapControl popup dismiss now uses a tunneling PointerPressed handler on the top-level window so clicks on controls outside the map (e.g. tracker items) properly close the location details popup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix input handling and map popup light-dismiss
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bump assembly versions to 3.0.1.9
The tunneling PointerPressed handler that closes the location details popup on outside clicks was also closing it when clicking inside child popups (e.g. the capture grid). Popup content renders on an overlay layer with a disconnected visual tree, so the walk from e.Source never reached LocationDetailsContent for nested popups. Fix: detect clicks inside popup overlays by checking whether the visual root of e.Source is the TopLevel window. If not, the click is inside a popup overlay and should not dismiss the location details. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix capture grid dismissed by map popup light-dismiss
- Remove case-insensitive ToLower() calls in ItemDatabase code provider indexing, preserving Lua's case-sensitive code lookups - Allow IClickHandler to intercept clicks on IgnoreUserInput items by making CanExecute always return true (guard moves to Execute, after IClickHandler check) - Remove IsHitTestVisible binding that blocked all interaction on IgnoreUserInput items - Add xpcall/debug.traceback wrapper (SafeCall) for all Lua call sites to capture full Lua call stacks in error output - Add dedicated LuaException formatting in developer console - Pass script path to DoString for better error source identification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix doors tracking and improve Lua error reporting
Child content that exceeds a GroupBox's bounds was visually overflowing instead of being clipped. Add ClipToBounds="True" to the outer Grid in the GroupBox DataTemplate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ClipToBounds to GroupBox layout element
Iterate temporary .ToList() snapshots of Repositories and Packages collections in UpdatesAvailable and CurrentPackageHasUpdateAvailable to avoid InvalidOperationException when collections are modified concurrently. Add null checks on entries for safety. Based on codemann8's fix (codemann8/EmoTracker@617da7a). Fixes #14 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix collection modified during enumeration in package update checks
- Add ActiveConnector property to AutoTrackerExtension as an alias for ActiveProvider, restoring compatibility with Lua scripts that rely on the old property name - Exclude [SNI] and [NWA] log messages from the developer console, as they are internal infrastructure concerns not relevant to pack developers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-state pulse - SniProvider now owns its ConnectionStatusChanged event, decoupled from the current DefaultDevice instance so subscriptions survive device changes during reconnect - RefreshDevicesAsync preserves existing SniDevice instances across refreshes (matched by URI) so event wiring stays stable - A scan timer starts on first RefreshDevicesAsync call (when the user selects the provider) and refreshes the device list every 5 seconds, removing stale entries and auto-selecting the first device when one becomes available - When autotracking is active, the loop also attempts reconnection every 5 seconds if the device is present but not connected - Added AvailableDevicesChanged event to IAutoTrackingProvider so the view layer can react to background device list updates - AutoTrackerExtension subscribes to AvailableDevicesChanged on SelectedProvider to auto-select a device and invalidate Start command availability without requiring manual provider re-selection - AutoTrackerExtensionView pulses the status icon grey<->teal (~4s cycle) when a valid provider and device are ready but autotracking has not yet been started Closes #46 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ViewBox DataTemplate: add Grid ItemsPanel so items use 4-value margin as absolute edge offsets instead of stacking vertically with the default StackPanel. Add Background binding (needed for semi-transparent overlays like start_location_view). Add ClipToBounds=false on ItemsControl. GroupBox DataTemplate: change content panel from StackPanel to Grid so children receive a finite height constraint. StackPanel measured children at infinite height, causing Viewbox-based content (map overlays) to scale based on width only—filling the width and clipping vertically on resize. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e PortAudio in Windows CI - Add AudioLibrariesAvailable property to VoiceRecognitionExtension; set false if Vosk or PortAudio native libraries fail to load - Status indicator shows red mic-slash icon and descriptive tooltip when libraries are unavailable; Active/Input Device context menu items are disabled in that state - CI workflows (build-avalonia and release) download the pre-built PortAudio x64 DLL from spatialaudio/portaudio-binaries and place it in the x64/ subdirectory of the Windows publish output (Windows only) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AppDomain.CurrentDomain.SetupInformation.ApplicationBase returns null in .NET Core/.NET 8, causing SetDllDirectory to receive a relative path that the loader cannot resolve. AppContext.BaseDirectory is the correct replacement and always returns the absolute path of the app's base directory. This fixes portaudio.dll (and any other x64/ bundled natives) failing to load on the published Windows build. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…bdir
The .NET runtime's native library probing automatically resolves DLLs
from runtimes/{rid}/native/ without needing SetDllDirectory. Revert the
AppContext.BaseDirectory change and instead place portaudio.dll in the
correct standard location in both CI workflows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove all #if WINDOWS conditional compilation blocks from App.axaml.cs: ConfigurePlatformDllPaths/SetDllDirectory call, Discord RPC teardown, and the SetDllDirectory P/Invoke declaration - Drop unused System.Runtime.InteropServices using directive - Place bundled portaudio.dll alongside the executable in CI output rather than in a runtimes subdirectory Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Drive locks Moves the .update-staging directory from inside the app install folder to %LOCALAPPDATA%\EmoTracker\.update-staging (on Windows) so that: - Windows Defender Controlled Folder Access cannot block the extraction step when the app is installed in Desktop or Documents (both are CFA-protected) - OneDrive / Known Folder Move cannot lock files mid-copy when those folders are being synced Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.update-stagingfrom inside the app install directory to%LOCALAPPDATA%\EmoTracker\.update-staging(Windows) /LocalApplicationData/EmoTracker/.update-staging(macOS/Linux)Test plan
%USERPROFILE%\Desktop\EmoTracker\and confirm it applies cleanly%USERPROFILE%\Documents\EmoTracker\and confirm it applies cleanly.update-stagingno longer appears inside the install directory after an update%LOCALAPPDATA%\EmoTracker\.update-stagingis created and cleaned up during the updateD:\Games\EmoTracker\)🤖 Generated with Claude Code