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>
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>
WebClient download handlers fire on ThreadPool threads. Mutating ObservableCollection (Clear/Add) off the UI thread causes CollectionChanged notifications to fire on the wrong thread, which races with UI-thread enumerations and produces the "Collection was modified; enumeration operation may not execute" InvalidOperationException. Parse JSON on the background thread as before, collect results into a local List<T>, then dispatch a single BeginInvoke to the UI thread to Clear/Add into mPackages and fire ForceRefreshProperty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a pre-flight check before applying updates on Windows. If Controlled Folder Access is enabled and the install directory is inside a default protected folder (Desktop, Documents, Pictures, Videos, Music), the xcopy swap script would be silently blocked. Now we detect this condition, show an actionable warning dialog styled to match the existing update UI, and abort cleanly instead of leaving a partially-applied update. - WindowsCfaChecker: reads CFA state from registry and checks whether the install path is under a default protected folder; no-ops on non-Windows - CfaWarningWindow: dark-themed dialog explaining the problem and listing resolution steps (move the app or add a Windows Security exception) - ZipInstallAndRelaunch: runs the check as the first step before any filesystem work Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x paths The macOS release archive packages a full EmoTracker.app bundle at its root, but the old LaunchUnixSwapScript treated installDir (Contents/MacOS) as the copy target — nesting the new bundle inside the old one and leaving the running binary unchanged. LaunchMacOSSwapScript now: - Derives the .app bundle path by walking up two levels from installDir - Removes the old bundle and copies the new one from staging in its place - Clears the com.apple.quarantine xattr so Gatekeeper doesn't block launch - Relaunches via `open` (required for proper bundle context on macOS) LaunchLinuxSwapScript retains the existing flat-file copy logic. Both Unix paths now use UseShellExecute = false for predictable behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dismiss, MCP tool (#53) - Render notifications with a drop shadow matching layout element shadow config - Apply type-specific left-border accent colors (Message=green, Celebration=cornflower blue, Warning=amber, Error=red) - Expose notification colors in ApplicationColors with JSON-configurable keys (notification_message/celebration/warning/error) - Add NotificationTypeToBrushConverter that reads live from ApplicationColors.Instance - Fix click-to-dismiss: replace Opacity=0 button overlay (Avalonia hit-test dead) with full-card Button; add ForceExpired event for immediate removal bypassing the timer - Add push_notification MCP tool for all four NotificationTypes Co-authored-by: EmoSaru <emosaru@emosaru.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…of style override (#54) Style setters (Style Selector="TabControl") have lower property-value priority than local values and are evaluated after the Fluent theme ControlTheme has already been applied, creating a window where the theme template can render tabs in its own order. Setting TabControl.Template directly as a local value ensures the custom template is in effect before the visual tree is attached, guaranteeing tabs render in their JSON definition order. Also inlines ItemsPresenter.ItemsPanel directly in the template rather than via a separate Style Selector="TabControl ItemsPresenter" setter, removing another style-timing dependency. Co-authored-by: EmoSaru <emosaru@emosaru.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore the VariantSwitcher status bar extension removed in a5dbe9c, following the same enable/disable pattern as the voice control extension. Disabled by default via a new EnableVariantSwitcher setting (also exposed through the MCP settings tool). The folder icon uses the #717171 default-state color to match other extension icons, and the context menu is populated programmatically on click so both left and right mouse buttons open the current pack's variant list reliably. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enable IsLightDismissEnabled on the notes Popup so clicking outside dismisses it, and sync the toggle button's IsChecked state back to false on Popup.Closed so the next click reopens the popup instead of silently toggling off. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the fixed sleep 1 with a kill -0 poll loop so the swap never races a still-exiting EmoTracker instance (mirroring the Windows tasklist loop). Launch the script via nohup + & so it survives the parent App bundle exiting and cannot be killed by SIGHUP. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…FileMode GetAwaiter().GetResult() on RunProcessAsync deadlocks when called from the Avalonia UI thread because the async continuation tries to resume on the same thread that is blocked waiting for it. Replace the chmod process spawn with File.SetUnixFileMode (available since .NET 7) which is synchronous and needs no child process. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redirect script stdout/stderr to /tmp/emotracker_update.log with set -x so every command and its result is visible after the update runs. Also log computed paths (installDir, appBundle, stagingDir) via Serilog before the script launches. Fixes CA1416 warnings by switching RuntimeInformation checks to OperatingSystem.IsX() guards the analyzer understands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Needed to produce a CI build of an older version than 3.0.1.15 so the update flow can be exercised end-to-end against a real release. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a quarantined app is launched from ~/Downloads, macOS Gatekeeper runs it from a randomized read-only mount under /AppTranslocation/. AppContext.BaseDirectory returns that translocated path, so the swap script's rm/cp target the read-only clone and fail silently — the real bundle is never updated and 'open' just relaunches the old version. Detect the translocation path prefix and show a dedicated warning window instructing the user to move the app out of Downloads, matching the existing Windows CFA pre-flight pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Update-flow testing is complete; bump back to 3.0.1.15 so the next release is forward from the previous public build. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The LayoutTransformControl for MapPanel defaults to Stretch alignment and inherits ClipToBounds=False from the LayoutControl-scoped style, so the scaled map could extend its hit-test region past its slot and swallow clicks on neighbouring layout elements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The release workflow now builds and publishes on tag push, so the skill bumps the version, pushes, and tags instead of manually downloading artifacts and creating the release with gh. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n outside map area
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
This PR merges the
avaloniabranch — a full port of EmoTracker from WPF to Avalonia, enabling cross-platform builds (Windows, macOS, Linux) — along with the many stability, autotracking, and UX fixes that have landed on the branch since it was cut.Highlights:
.axaml/code-behind.External/NDI/NDILibDotNet2and distributables removed as part of the port.183 commits; full log available on the branch.
Test plan
🤖 Generated with Claude Code