Commit d42f80b
Plan A: Windows app cutover from WPF to Avalonia (#19)
* Plan A Phase 3 WIP: Avalonia foundation (build broken, foundation only)
First chunk of the WPF -> Avalonia main-UI cutover. Lands the
toolkit-neutral foundation so subsequent sessions can iterate per-window
without re-litigating settled decisions. See
ai-docs/plans/windows-avalonia/PHASE3_WIP_STATUS.md for the full
status, decisions, and remaining work.
Locked in this session:
- Avalonia 11.2.3 + Fluent theme (system variant) + Inter font.
- In-place retarget of Mouse2Joy.App (no new host project).
- Fluent Tooltip.Build().Typical(...).Description(...).Advice(...) API +
{tt:Tooltip ...} markup extension. Visible rendering preserved.
- Panic hotkey rehosted on a self-owned Win32 message-only window +
dedicated message-pump thread in Platform.Windows (no HwndSource).
- UiThread helper in Mouse2Joy.UI wrapping Dispatcher.UIThread.
No new IDispatcher port in Platform.Abstractions.
- Toolkit-agnostic Interop (WindowStyles takes raw HWND, MonitorInfo
uses a PixelRect struct, no System.Windows dependency).
Build is intentionally red on this branch: the WPF views/controls were
deleted to make room for the Avalonia rewrites in chunks A-F. The first
follow-up session ("chunk A") brings the build green by porting the
custom controls + overlay widgets + stubbing the 5 windows so the AXAML
re-author can land per-window from there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3 Chunk A: green build with Avalonia stubs
Restores `dotnet build Mouse2Joy.sln` (0 warnings, 0 errors) and
`dotnet test Mouse2Joy.sln` (348/348 passing) on the Avalonia branch.
UI is still placeholder text in each window; chunks B–F land the real
AXAML.
Ports:
- Custom controls: KeyCaptureBox, CurveEditorCanvas, ChainPreviewControl
to Avalonia primitives (StyledProperty, Pointer events, FormattedText
new ctor, AffectsRender, StreamGeometryContext.LineTo/EndFigure).
- PlaceholderText deleted — Avalonia's built-in TextBox.Watermark covers
the case; AXAML call sites use it directly during chunks B–D.
- 8 overlay widgets to Avalonia Control + Render(DrawingContext):
Background, Button, ButtonGrid, Axis, TwoAxis, MouseActivity,
EngineStatusIndicator, Status. Status preserves the 644-line per-glyph
layout; switched WPF RotateTransform(angle, cx, cy) to composed
Matrix.CreateTranslation*CreateRotation*CreateTranslation and fixed
non-mutating Rect.Union by reassigning each step.
- OverlayWidget base: IBrush/IPen, Color.TryParse, DrawRectangle(...,
radiusX, radiusY) for rounded rects.
- OverlayCoordinator + OverlayWidgetHost on Avalonia Canvas; dispatcher
marshaling via UiThread; window placement delegated to
OverlayWindow.ApplyMonitor(MonitorInfo) so the PixelPoint-vs-DIP
split lives in one place.
- MonitorInfo: dropped our PixelRect struct in favour of
Avalonia.PixelRect (same shape, avoids name collision in view code);
re-added BoundsDip as a tuple for DIP-space sizing.
Wider WPF removal:
- WindowsGlobalHotkey rewritten with the same self-owned Win32 message-
only window pattern as PanicHotkey. Cross-thread RegisterHotKey is
ferried to the pump thread via PostMessageW(WM_APP_REGISTER) because
the call is tied to the registering thread's queue. The whole
Mouse2Joy.Platform.Windows assembly is now WPF-free.
- Platform.Windows.csproj drops <UseWPF> and the Hardcodet WPF tray
package; tray is now Avalonia in Mouse2Joy.App.
- Mouse2Joy.UI.Tests drops <UseWPF>.
- ModifierParamProxies.OpenEditor uses
IClassicDesktopStyleApplicationLifetime.Windows to find the owner
and calls ShowDialog(owner) instead of WPF's Window.Owner.
Packages:
- Added Microsoft.Win32.SystemEvents 8.0.0 (used to come in via WPF;
OverlayCoordinator hooks DisplaySettingsChanged for monitor hotplug).
Stub windows authored so the composition root compiles:
- MainWindow, BindingEditorWindow, WidgetEditorWindow, CurveEditorWindow
(single TextBlock each), and OverlayWindow with full click-through
wiring via WindowStyles.MakeOverlay on Opened + 60 Hz DispatcherTimer
sampling InputEngine.Current. Real layouts land in chunks B–F.
PHASE3_WIP_STATUS.md updated with the chunk-A completion record and the
remaining chunk-B-through-F roadmap.
Verification: dotnet build succeeds (0/0); dotnet test passes
348/348 across Contracts (61), Platform.Abstractions (5), UI (9),
Persistence (73), Engine (200). The app has not been launched yet —
there is no real UI to validate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3: lock in 9 carry-over + Chunk-A-surfaced decisions
Replaces the "Open questions for next session" list with a "Decisions
locked in" block so chunks B-F don't re-litigate settled choices:
- Compiled bindings everywhere (no `{Binding}` fallback; refactor or
use explicit `{CompiledBinding}` with `x:DataType` for dynamic
DataContexts like BindingEditorViewModel.SelectedProxy).
- Theme-aware brushes (`DynamicResource SystemControlForegroundBaseMediumLowBrush`
or closest equivalent) for dim/hint text; drop hard-coded DimGray.
- DPI manifest stays PerMonitorV2 (confirmed, no action).
- Tray icon: ship a minimal .ico embedded as a resource (asset work
in chunk B).
- Overlay tick rate stays 60 Hz DispatcherTimer per S2 spike;
documented semantic change from WPF v1's InputEngine.Tick subscription.
- OverlayWindow parameterless ctor gates on Design.IsDesignMode so the
Avalonia previewer doesn't run EnumDisplayMonitors against the host.
- Trust SystemEvents.DisplaySettingsChanged for display hotplug;
smoke-test during chunk D, fall back to WM_DISPLAYCHANGE subclass
per OverlayWindow only if it doesn't fire under Avalonia.
- BindingEditorWindow ctors stay () and (Binding?); window owns its
VM internally.
- StatusWidget per-glyph rotation correctness validated by chunk E
manual walkthrough; regression test added only if it visibly breaks.
- Application.ShutdownMode = OnExplicitShutdown confirmed; closing the
main window minimizes-to-tray.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3 Chunk B: MainWindow + tray icon
Re-author MainWindow.axaml as the real 5-tab UI (Profiles, Hotkeys,
Overlay, Settings, Setup), replacing the Chunk A green-build stub.
Compiled bindings, theme-aware brushes, and fluent tooltips throughout.
Avalonia idiom adaptations in the code-behind:
- MouseLeftButtonUp -> PointerReleased with MouseButton.Left guard
- CheckBox.Click -> IsCheckedChanged, with load-time suppression flags so
settings don't get re-saved on window-open
- ListView -> ListBox; per-item style via <ListBox.Styles>
- WPF DataTrigger italic+dim cue -> Avalonia class selector
(Classes.auto-label="{Binding IsAutoLabel}")
- ShowDialog awaited; dialog-opening handlers are async void
- MessageBox.Show -> WindowsMessageBox.Warn / AskYesNoCancel
- Application.Current.Shutdown() ->
IClassicDesktopStyleApplicationLifetime.Shutdown()
Moved WindowsMessageBox from Mouse2Joy.App to Mouse2Joy.Platform.Windows
so UI code-behind can call it without UI depending on App. Added
AskYesNoCancel for the destructive remove-widget-with-children prompt.
Stub editor windows updated for the new dialog flow:
- BindingEditorWindow.Result (Binding?) -- left null in Chunk B
- WidgetEditorWindow.Result (WidgetConfig?) plus the (existing,
siblings, monitors) ctor matching the WPF call shape
Branded 32x32 joypad-glyph Mouse2Joy.ico generated and wired:
- Packaged as <AvaloniaResource> in Mouse2Joy.App.csproj
- Tray icon loads via AssetLoader.Open("avares://Mouse2Joy/Assets/...")
- MainWindow consumes via Icon="avares://..." in AXAML
- Set as the exe's ApplicationIcon
Verification: dotnet build green (0/0); dotnet test 348/348 passing.
The app still has not been launched -- overlay window and the binding /
widget editors are stubs; full manual walkthrough is Chunk E.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3 Chunk C: BindingEditor + CurveEditor
Re-author the modifier-chain binding editor and the curve-editor popout
as full Avalonia windows. Build remains green; 348/348 tests still pass.
BindingEditorWindow
- 6-section layout matching the WPF original: Label, Source/Target +
Suppress, modifier chain (list + selected-card param pane), preview +
auto-insert notice, validation banner, OK/Cancel.
- All 20 modifier-proxy DataTemplates inlined in Window.DataTemplates.
ResourceDictionary cannot host unkeyed DataTemplate children
(AVLN3000); a side ResourceInclude is not an option in Avalonia.
- Compiled bindings on every binding; x:DataType per template.
SelectedValueBinding on the 4 enum combos uses ReflectionBinding for
the item-level Tag binding (Tag lives on ComboBoxItem, not the proxy).
- Modifier-card selected affordance via class selector
(Classes.selected="{Binding Selected}") instead of WPF DataTrigger.
- ListBoxItem chrome scoped via Style Selector="ListBox.chain-list
ListBoxItem" so the system selection highlight doesn't fight the
card's dark background.
- KeyCaptureBox commit subscribes to CapturedKeyProperty observable so
the VM sees a fresh KeySource the moment the user presses a key.
- Auto-label preview surfaced via the TextBox's built-in Watermark
property, mirrored from the VM's AutoLabel on Source/Target changes.
- Unbound KeySource guard surfaces through WindowsMessageBox.Warn (same
Win32 helper used elsewhere); validation banner uses InvBoolConverter.
CurveEditorWindow popout
- 4-row layout (canvas / Symmetric + Points / hint / Close) hosting
CurveEditorCanvas from Chunk A.
- CurveEditorWindowViewModel promoted to public so AXAML compiled
bindings can resolve x:DataType against it. Linear resampling on
PointCount change preserved from the WPF original.
MainWindow needed no edits; its add/edit/duplicate flows already check
dlg.Result, and the editor now populates it on OK.
Verification
- dotnet build Mouse2Joy.sln — 0 warnings, 0 errors.
- dotnet test Mouse2Joy.sln — 348/348 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3 Chunk D: WidgetEditorWindow
Port the WPF widget Add/Edit dialog to Avalonia with the 17-row scaffold
preserved (Type / Label / Visible / Position / Monitor / Parent / Anchor
+ Self anchor / Offset X+reset / Offset Y+reset / Size / Width / Lock+
Swap / Height / Font (Status-only) / Options / Cancel-Save) and the
dynamic Options + Font panels rebuilt against Avalonia controls.
- Use the built-in Avalonia NumericUpDown (decimal-based) in place of
the WPF custom one; convert at the staging-record boundary.
- Lock-aspect + B/I/U "checked" tint via a scoped
Style Selector="ToggleButton.toggle-highlight:checked
/template/ ContentPresenter"
replacing WPF's triggered-style override.
- ToolTip.SetTip for dynamic tooltip swaps (lock chip, monitor combo).
- TextBox.Watermark-based auto-label preview, matching the BindingEditor
pattern from Chunk C.
- ItemsControl ItemsSource re-poke idiom for the dynamic Options + Font
panels (Avalonia doesn't observe in-place List mutations).
- FontManager.Current.SystemFonts replaces WPF SystemFontFamilies.
- Color.TryParse replaces ColorConverter.ConvertFromString; brushes no
longer Frozen.
- (IBrush?) cast on Brushes.Transparent for well-typed ?? coalesce.
OverlayWindow needed no config-mode chrome — it was permanently click-
through in WPF too — so the Chunk A stub is already the production
shape. PHASE3_WIP_STATUS.md updated with the Session 3 Chunk D log and
the remaining-work list reduced to Chunks E + F.
Verification:
- dotnet build Mouse2Joy.sln: 0 warnings, 0 errors.
- dotnet test Mouse2Joy.sln: 348/348 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3 Chunk E: tooltip call-site sweep
Reviewed every tooltip across the 5 windows against the WPF originals
and the TOOLTIP_AUTO_WRAP.md convention. Chunks B-D preserved tooltip
shape 1:1 with WPF, so the sweep is a minimal-delta outcome:
- WidgetEditor Label TextBox: upgraded the auto-label hint from a plain
string to {tt:Tooltip Typical='Leave blank to auto-label by Type (and
#N when there are siblings)', Description='...'} so the Typical line
surfaces the "what happens if I leave this empty" answer where it's
most useful (parallels BindingEditor's LabelTooltip resource).
- Added the tt: xmlns to WidgetEditorWindow root for the markup
extension.
Remaining ~13 plain-string tooltips correctly stay plain (single-thought
content with no Typical/Advice line) and route through the app-wide
ToolTip MaxWidth=320 auto-wrap style.
DimGray sweep clean: 0 matches across src/Mouse2Joy.UI (Chunk-A
theme-aware-brushes decision fully landed).
PHASE3_WIP_STATUS.md updated with the Chunk E session block and a full
user-driven manual walkthrough checklist (per-window, cross-cutting,
regression scan) covering the Phase 3 exit gate that requires elevated
shell + hardware.
Verification:
- dotnet build Mouse2Joy.sln: 0 warnings, 0 errors
- dotnet test Mouse2Joy.sln: 348/348 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Plan A Phase 3+4 Chunk F: implementation write-down
Adds the Plan A summary write-down per repo convention. Captures the
WPF → Avalonia cutover as a single Context / What-changed / Key-decisions
/ Files-touched / Follow-ups record so future work doesn't have to
reconstruct the rationale from the per-chunk PHASE3_WIP_STATUS diary.
Phase 4 WPF-removal confirmation:
- grep across src/ + tests/ for PresentationCore, PresentationFramework,
WindowsBase, System.Xaml, System.Windows.{Controls,Media,Shapes,...},
<UseWPF>, Hardcodet.NotifyIcon.Wpf returns 0 hits.
- System.Windows.Input.ICommand in ModifierParamProxies is the BCL type
(System.ObjectModel.dll), not WPF.
Manual UI walkthrough was run by the user on plan-a-avalonia-wip on
2026-05-21 and PASSed (5-window + cross-cutting + regression checklist
from PLAN.md). Recorded in the write-down.
Verification:
- dotnet build Mouse2Joy.sln: 0 warnings, 0 errors
- dotnet test Mouse2Joy.sln: 348/348 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix dotnet format violation in MainWindow.axaml.cs
CI's `dotnet format Mouse2Joy.sln --verify-no-changes` rejected an
aligned-column layout in the AnchorPointOnRect switch expression
(arrows + arguments padded with extra spaces to line up across the
9 cases). The repo's analyzer rules don't allow trailing whitespace
inside expression arms, so the alignment had to go. Behavior
unchanged; formatter output verified clean (exit 0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address PR review feedback (PR #19)
Fixes the 6 inline review comments from Copilot:
1. KeyCaptureBox: Extend static MapScancode table to cover NumPad 0-9
+ decimal/multiply/divide/add/subtract, US OEM punctuation (~ - = [
] \ ; ' , . / + backslash), and the lock/special keys (CapsLock,
NumLock, Scroll, PrintScreen, Pause). For anything still uncovered
(browser/media/launcher keys, locale-specific OEM), add a Win32
MapVirtualKeyW(vk, MAPVK_VK_TO_VSC_EX) fallback so the captured
scancode is correct instead of silently dropping to PhysicalKey.None.
2. UiThread.Invoke: rewrite the doc to accurately describe post-on-off-
thread semantics ("queued asynchronously when off the UI thread")
and add a new InvokeAsync(Action) -> Task for callers that actually
need wait-for-completion across threads.
3. WindowsGlobalHotkey.Register: bound the pending.Done.Wait on a 5 s
timeout. On timeout, remove the pending entry under the lock and
throw TimeoutException so a wedged pump thread cannot deadlock the
caller (e.g. UI thread during startup).
4. WindowsGlobalHotkey.Dispose: replace PostMessageW(_hwnd, WM_QUIT, ..)
(WM_QUIT is a thread-queue message, not dispatched to a WndProc)
with a custom WM_APP_QUIT that the WndProc handles by calling
DestroyWindow -> WM_DESTROY -> PostQuitMessage. Guarantees the pump
loop exits via the documented Win32 shutdown idiom. Dropped the now-
unused WM_QUIT constant.
5. PanicHotkey.Dispose: same WM_APP_QUIT fix as WindowsGlobalHotkey.
6. BindingEditorWindow: store the IDisposable returned by Subscribe on
KeyCaptureBox.CapturedKeyProperty and dispose it (plus unhook
PropertyChanged) on the window's Closed event so the dialog can be
GC'd cleanly after close.
Verification:
- dotnet build Mouse2Joy.sln -c Release: 0 warnings, 0 errors
- dotnet test Mouse2Joy.sln: 348/348 pass
- dotnet format Mouse2Joy.sln --verify-no-changes: clean (exit 0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* BindingEditor: refresh ChainPreview on modifier collection + card changes
Copilot review on PR #19 (comment 3277855467) flagged that the chain
preview goes stale during edit because BindingEditorViewModel never
raises PropertyChanged(nameof(Modifiers)) for add / remove / reorder /
per-card edits — the VM uses a stable ObservableCollection reference,
with per-card mutations flowing through ModifierCardViewModel's
ModifierChanged event.
Hook _vm.Modifiers.CollectionChanged + each card's ModifierChanged,
refresh the preview from both, keep per-card hooks in sync on
collection mutations, and unhook everything on the window's Closed
event. Also dropped the dead nameof(Modifiers) arm from
OnVmPropertyChanged with a comment explaining the new routing.
Verification:
- dotnet build Mouse2Joy.sln -c Release: 0 warnings, 0 errors
- dotnet test Mouse2Joy.sln: 348/348 pass
- dotnet format Mouse2Joy.sln --verify-no-changes: clean (exit 0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address PR #19 review findings: critical + perf + stability + UX
Critical:
- OverlayCoordinator: guard DisplaySettingsChanged dispatch on _disposed
so a late-arriving display event after Dispose doesn't resurrect closed
overlay windows (which would leak their 60Hz timers for process lifetime).
- AvaloniaTrayIcon: rebind WindowNotificationManager when the MainWindow
TopLevel changes (close-to-tray destroys the prior window; the cached
notifier was targeting a dead TopLevel and silently dropping toasts).
- WindowsGlobalHotkey: hold _gate across WM_APP_REGISTER handler so the
Register() timeout-cleanup path can't dispose the ManualResetEventSlim
between TryGetValue and Set (which would throw ObjectDisposedException
out of DispatchMessageW and kill the pump). Also handle the race-won
case where the pump completes after Wait() times out.
- BindingEditor: add HeaderedContentControl.groupbox style emulating the
WPF GroupBox chrome that FluentTheme strips by default.
Perf (60Hz overlay hot path):
- OverlayWidget: cache parsed brushes/pens per Config instance (was
allocating a fresh SolidColorBrush every Read on every frame).
- ButtonGridWidget: hoist Typeface to static field; cache 15
FormattedText instances keyed on font size.
- StatusWidget: memoize RenderPlan against a key of every option +
resolved snapshot text; drive size from MeasureOverride/InvalidateMeasure
instead of mutating Width/Height inside Render (which inverted the
Avalonia layout flow).
- MouseActivityWidget: cache the arrow Pen keyed on (accent, thickness).
- EngineStatusIndicatorWidget: hoist fallback brushes to static fields.
- OverlayWindow: pause the 60Hz tick timer when IsVisible=false (was
walking every widget + InvalidateVisual while Hide()d).
Stability:
- PanicHotkey: add ReadyTimeout guard on Register, double-dispose guard,
separate _registered flag (so UnregisterHotKey runs even if a later
exception flipped _registerOk back), snapshot HWND into the pump-thread
local for finally cleanup, log every swallowed Win32/pump exception.
- WindowsGlobalHotkey: snapshot HWND for finally; log pump exceptions
instead of silently swallowing.
- App.Teardown: log every per-step swallowed exception (shutdown is when
you most want diagnostics; Log.CloseAndFlush still flushes after).
- OverlayWindow: only start tick timer from OnOpened (not AttachEngine);
re-pin overlay styles in ApplyMonitor (OS can reset layered/topmost
state after DPI/resolution change).
- WindowStyles: route SetWindowLongPtr / SetWindowPos failures through
Serilog instead of Debug.WriteLine.
Correctness:
- KeyCaptureBox: drop the Pause => (0x45, false) arm (collided with
NumLock's HID slot, silently misbinding to NumLock). Let the
MapVirtualKey fallback surface unsupported instead.
- CurveEditorCanvas.PixelToCurve: early-return on zero size to prevent
NaN from a pre-layout pointer event propagating into persisted curve
points.
- Linear curve resamplers (ModifierParamProxies, CurveEditorWindow):
guard against two control points sharing an X (divide-by-zero produced
NaN that propagated through Fritsch-Carlson math).
UX:
- MainWindow: rename "Activate (SoftMuted)" to "Activate (soft mute)"
(drop internal enum jargon).
- MainWindow: re-add UpdateSourceTrigger=PropertyChanged on Profile name
+ Tick rate Hz TextBoxes so dependent UI updates live.
- MainWindow: drop redundant "(Start with Windows is not yet wired up)"
helper line — Avalonia shows tooltips on disabled controls.
- BindingEditor: switch all 32 modifier-param TextBoxes to
UpdateSourceTrigger=PropertyChanged so typing drives the sibling slider
and chain preview live.
- BindingEditor: replace 4 `{ReflectionBinding Tag}` lookups with
`{Binding Tag, DataType=ComboBoxItem}` (compiled).
- CurveEditorWindow: add Mode=TwoWay to the point-count TextBox binding.
Docs:
- PLAN_A_WINDOWS_AVALONIA.md: reconcile the tooltip-dim claim with the
actual code (Opacity=0.7 on the TextBlock + SystemControlForeground
BaseMediumLowBrush on chrome — explain when each is used).
- TOOLTIP_AUTO_WRAP.md: add a Plan A update section noting the WPF
mechanics described above are gone and the user-facing rendering
contract is preserved; new authoring is the fluent builder + {tt:Tooltip}
markup extension.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 7af37dc commit d42f80b
62 files changed
Lines changed: 5414 additions & 3499 deletions
File tree
- ai-docs
- implementations
- plans/windows-avalonia
- src
- Mouse2Joy.App
- Assets
- Mouse2Joy.Platform.Windows
- Mouse2Joy.UI
- Controls
- Converters
- Interop
- Overlay
- Widgets
- Tooltips
- ViewModels
- Editor
- Views
- Editor
- tests/Mouse2Joy.UI.Tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | 5 | | |
7 | 6 | | |
8 | 7 | | |
| |||
11 | 10 | | |
12 | 11 | | |
13 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
14 | 26 | | |
15 | 27 | | |
16 | 28 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
0 commit comments