Commit 522c940
spec(047): Phase 3-final descriptor scale-out (Batches A-G1) (#436)
* spec(047): Phase 3-final Batch A — engine shapes for descriptor scale-out
Lands the new builder/entry/strategy surface the Phase 3 bulk-port session
(PR #435) was constrained from inventing. Subsequent batches port the
remaining controls against these shapes.
New shapes (all [Experimental("REACTOR_V1_PREVIEW")]):
- `.OneWayBridged<TValue>` — set lambda receives Reconciler + rerender,
for descriptors that bridge to engine-internal helpers (Flyout,
future bridged transforms). PropEntry base grows virtual
context-carrying Mount/Update overloads; existing entries forward
to the parameterless overloads via the virtual default.
- `.Immediate<TPayload>` — pure subscription wiring for the
"observed-DP property-changed callback + Loaded → inner template-part
trampoline" pattern. Used by NumberBox's per-keystroke immediate-mode.
Author supplies captured-free static callbacks; the entry manages
per-control payload slot bookkeeping. No DP write (the sibling
.HandCodedControlled handles the commit-mode round-trip).
- `.CollectionDiffControlled<TPayload,TItem,TKey,TDelegate>` — two-way
bound IList<T> prop (e.g. CalendarView.SelectedDates). Each Update
applies a hash-set diff and emits per-item Add/Remove inside
ChangeEchoSuppressor.BeginSuppress.
- `Panel<>.PerChildAttached` — additive optional callback invoked after
each child mount/reconcile. Lets Grid/Canvas/Flex/RelativePanel/WrapGrid
descriptors write WinUI attached DPs (Grid.SetRow, Canvas.SetLeft, ...)
based on attached-prop hints on the child element. Default null =
no-op; StackPanel etc. unaffected.
- `ItemsHost<TElement,TControl>` rewrite — new shape with GetItems
(IReadOnlyList<object>) + GetCollection (IList) + optional ItemEquals.
V1HandlerAdapter now dispatches it: clear + populate on Mount,
ItemEquals-gated rebuild on Update. Targets ListBox/ComboBox.Items/
RadioButtons items (Batch G1). Typed templated lists (ListView<T>
etc., Batch G2) keep delegate-body handlers with internal spec-042
keyed reconcile until the typed ports land their own descriptors.
- `Reconciler.CreateFlyoutForDescriptor` — null-tolerant sibling of
CreateFlyoutFromElement, mirrors the ResolveIconForDescriptor naming
for descriptor-facing bridges (Batch D).
NumberBoxEventPayload extended with ImmediateTextChangedCallback +
ImmediateInnerTextChangedTrampoline + ImmediateInnerWired flag slots
for the NumberBox descriptor port (Batch B).
ListViewHandler drops the placeholder ItemsHost strategy declaration
(Children => null). The delegate body already owns all dispatch via
MountListView/UpdateListView; declaring a strategy on top would
double-handle. ItemsHost is now reserved for descriptor authors of
flat items collections and the typed ListView<T> port in Batch G2.
Self-test V1 ON/OFF Desc_ filter: # Total failures: 0 (50 descriptors
unchanged). 7 new surface-level unit tests in
tests/Reactor.Tests/Spec047/V1Protocol/Phase3FinalEntryShapesTests.cs;
existing ChildrenStrategyTests / ListViewPortTests migrated to the
new ItemsHost / Children=null shapes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec(047): Phase 3-final Batch B — Frame + RichTextBlock + NumberBox
Ports three descriptors against the Batch A engine shapes:
- FrameDescriptor uses .Initial<(Type?, object?)> for the mount-only
Navigate call (no-op when SourcePageType is null; never re-navigates
on Update — matches legacy UpdateFrame) and .HandCodedEvent x3 for
Navigated/Navigating/NavigationFailed against a new FrameEventPayload.
- RichTextBlockDescriptor uses a single .OneWay<RichTextBlockElement>
with a custom ReferenceEqualityComparer that gates rebuild on the
Paragraphs array reference (+ Text fallback for the no-Paragraphs
case). The set lambda calls the shared
Reconciler.RebuildRichTextBlocks helper (widened internal static,
also invoked by the legacy MountRichTextBlock arm).
- NumberBoxDescriptor uses .HandCodedControlled for the Value/
ValueChanged round-trip plus .Immediate<NumberBoxEventPayload> for
the per-keystroke text observation (TextProperty + Loaded → inner
TextBox via the existing NumberBoxImmediateTextChanged +
EnsureNumberBoxImmediateTextBoxWiring helpers, widened internal
static for descriptor sharing).
Documented gaps:
- FrameDescriptor's three .HandCodedEvent entries gate subscription
on the callback being present at Mount time (standard contract).
Legacy MountFrame subscribes unconditionally so a late-attached
callback fires through the same trampoline; descriptor matches the
established §14 EnsureXxxWiring contract — fully covers the
callback-on-mount common case.
- RichTextBlockDescriptor uses reference-equality on Paragraphs;
the legacy UpdateRichTextBlock arm does an incremental per-
paragraph / per-inline diff. Authors who need that incremental
shape stay on V1 OFF. Static-hoisted arrays skip rebuild entirely.
- NumberBoxDescriptor Min/Max are plain .OneWay (no .CoercingOneWay)
so a Min/Max widen that coerces Value echoes through OnValueChanged
— legacy arm wraps coercion in BeginSuppress. Authors needing
coercion-tolerance opt in to .CoercingOneWay later.
- NumberBoxDescriptor's Value entry doesn't consult nb.Text before
writing — the legacy arm's ImmediateValueAttached "skip Value
write when typed text is non-canonical" path stays V1 OFF for now.
Self-test V1 ON Desc_ filter: all 6/6 Frame + 7/7 RichTextBlock +
15/15 NumberBox checks pass; total failures 0. V1 OFF Desc_ filter:
total failures 0 (legacy RichTextBlock rebuild path verified intact
after MountRichTextBlock was switched to the shared helper).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec(047): Phase 3-final Batch C — CalendarView via .CollectionDiffControlled
Ports CalendarView as the proof point for Batch A's new
.CollectionDiffControlled<TPayload,TItem,TKey,TDelegate> entry shape.
- SelectedDates IObservableVector<DateTimeOffset> is two-way bound via
the new entry — Mount fills the vector bare; Update applies a
UtcTicks-keyed hash-set diff inside one BeginSuppress so per-mutation
echo can'\''t fire back through OnSelectedDatesChanged.
- New CalendarViewEventPayload (single SelectedDatesChangedTrampoline
slot) added to ControlEventPayloads.cs.
- All other props as .OneWay / .OneWayConditional matching the legacy
arm'\''s classification (SelectionMode, IsGroupLabelVisible,
IsOutOfScopeEnabled, NumberOfWeeksInView, DisplayMode,
CalendarIdentifier, Language gated on IsWellFormed, MinDate, MaxDate,
FirstDayOfWeek).
Documented gaps:
- Legacy UpdateCalendarView treats null SelectedDates as "uncontrolled,
don'\''t reconcile"; the descriptor projects null -> Array.Empty so
flipping from non-null back to null clears the vector. Callsite
expectation: always pass a list (possibly empty) when selection is
controlled.
- Legacy MountCalendarView subscribes to SelectedDatesChanged
unconditionally; descriptor gates subscription on OnSelectedDatesChanged
being present at Mount (standard §14 EnsureXxxWiring contract).
Self-test V1 ON/OFF Desc_ filter: 438/438 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec(047): Phase 3-final Batch D — Button-family Flyout via .OneWayBridged
Wires DropDownButton, SplitButton, and ToggleSplitButton descriptors
to use Batch A's .OneWayBridged<TValue> entry shape + the null-tolerant
Reconciler.CreateFlyoutForDescriptor sibling helper. Closes the
"Flyout escape-hatched" known gap on all three descriptors.
Each gets a single .OneWayBridged<Element?> entry whose set lambda
calls (rec, rr) => c.Flyout = rec.CreateFlyoutForDescriptor(v, rr).
Reference-equality comparer (per-descriptor private ElementReferenceComparer,
matches the GridDescriptor.GridDefinitionReferenceComparer pattern) —
the rebuild only fires when the Flyout Element reference changes;
EqualityComparer<Element?>.Default would do record-style structural
equality and could miss content swaps that produced a structurally
equal Element.
Three new self-test fixtures (Desc_DropDownButton_Flyout,
Desc_SplitButton_Flyout, Desc_ToggleSplitButton_Flyout) with 12 total
checks covering Attached, NullOnNullInput, SwappedOnReconcile, and
PreservedOnSameRef across all three descriptors.
Documented gaps:
- ReferenceEqualityComparer.Instance (System.Collections.Generic) types
as IEqualityComparer<object?>, not IEqualityComparer<Element?>, so each
descriptor declares a private ElementReferenceComparer rather than
sharing a single static instance. Acceptable — mirrors the existing
GridDescriptor.GridDefinitionReferenceComparer pattern. A future
cleanup could hoist this to a shared internal helper if more
descriptors need reference identity over Element?.
Self-test V1 ON/OFF Desc_ filter: 450/450 pass on both modes (was
438/438 + 12 new flyout checks = 450).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec(047): Phase 3-final Batch E — Panel per-child attached props + WrapGrid
Wires Grid/Canvas/FlexPanel descriptors to use Batch A's additive
Panel<>.PerChildAttached callback. Each panel descriptor declares a
captured-free set lambda that reads the child element's attached-prop
hint (GridAttached / CanvasAttached / FlexAttached) and writes the
corresponding WinUI attached DP. Mirrors what the legacy MountXxx arms
do after each child mounts; closes the "descriptor-mounted children
stack at row 0 / column 0" known gap from PR #435 batch 8.
WrapGridDescriptor (new) — closes the Phase 3 batch 8 escape-hatch
("WrapGrid escape-hatched (needs per-child attached-prop hook)").
Mirrors MountWrapGrid: Orientation always written; MaximumRowsOrColumns
/ ItemWidth / ItemHeight via .OneWayConditional; per-child WrapGridAttached
(RowSpan / ColumnSpan) via PerChildAttached. Registered in the perf
DescriptorVariantFactory alongside the other panel descriptors.
FlexPanelDescriptor delegates to Reconciler.ApplyFlexAttached (promoted
from private to internal) so descriptor and legacy paths share the
"always apply — reset to defaults when no hint" semantics that protect
against stale Yoga config on pool-rented controls. CanvasDescriptor
delegates to Reconciler.ApplyCanvasPosition (already internal) so the
anchor-state ConditionalWeakTable + SizeChanged wiring is shared.
Each descriptor's PerChildAttached callback resets the relevant WinUI
attached DPs via ClearValue when the child has no hint, so a reordered
child whose attached prop drops between renders is not stuck with the
prior placement.
Documented gaps:
- RelativePanelDescriptor carved to Phase 4: the per-child callback
fires sequentially during the mount loop, so name references like
RightOf="foo" can't resolve against siblings that haven't been
mounted yet. Needs either a post-loop second-pass shape on Panel<>
or a dedicated NamedRelativePanel strategy. Doc updated in-place.
Self-test V1 ON/OFF Desc_ filter: 494/494 pass on both paths
(includes 4 new fixtures: Desc_Grid_AttachedRowColumn,
Desc_Canvas_AttachedLeftTop, Desc_FlexPanel_AttachedFlexProps,
Desc_WrapGrid_MountUpdate).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec(047): Phase 3-final Batch F — Image events + Path.Data + InfoBar.ActionButton
Three more descriptor-surface ports. ImageDescriptor adds ImageOpened/ImageFailed via .HandCodedEvent over the existing ImageEventPayload (event entries declared before Source .OneWay so subscriptions land before the cached-image synchronous fire). PathDescriptor adds the pre-built Geometry Data path as .OneWayConditional (reference comparer, gated on PathDataString being null so the legacy XamlReader/parser path stays the single owner for that surface) plus FillRule propagation onto PathGeometry. InfoBarDescriptor adds ActionButton as .OneWayBridged<string?> that builds the inner Button and wires Click via a static-trampoline reading the parent InfoBar's Tag — record-with that swaps OnActionButtonClick picks up automatically.
Shipped:
- Image: ImageOpened + ImageFailed .HandCodedEvent entries (live-element via Tag)
- Path: Data .OneWayConditional + FillRule .OneWayConditional onto PathGeometry
- InfoBar: ActionButton .OneWayBridged with Click trampoline
Carved to Phase 4:
- Expander.HeaderTemplate: requires a NamedSlots strategy, conflicts with the existing SingleContent Children — only one Children strategy per descriptor today.
- TeachingTip.Target: cross-element reference resolution (Target points at a sibling's mounted control); descriptor framework can't reference another element's resolved native control.
- Path.PathDataString: legacy XamlReader + PathDataParser strategies need string-diff-against-old-element comparer and old+new+xaml+parser error context the engine's per-prop comparer can't replicate.
- Icon polymorphic: already done in existing descriptors (InfoBar/TeachingTip/AutoSuggestBox/SelectorBar use Reconciler.ResolveIconSource / ResolveIconForDescriptor).
V1 ON: 511 ok / 0 failures
V1 OFF: 511 ok / 0 failures
* spec(047): Phase 3-final Batch G-prep — ItemsHost typing + descriptor-side ordering
Two engine fixes uncovered by the first Batch G1 attempt (which reverted
clean rather than ship broken). ItemsHost is now usable for descriptor
authors; flat-items ports (ListBox / ComboBox / RadioButtons in G1) and
typed templated lists (G2 keyed-reconcile shape, deferred) build on top.
Changes:
- ChildrenStrategy.ItemsHost.GetCollection signature: System.Collections.IList -> IList<object>.
WinUI Microsoft.UI.Xaml.Controls.ItemCollection does not implement
the non-generic IList projection under CsWinRT; a descriptor reaching
for c.Items hit InvalidCastException at runtime. IList<object> is the
projected interface ItemCollection actually exposes.
- DescriptorHandler.Mount / Update now dispatch ItemsHost INLINE between
RentControl and the prop loop (Mount), and before the prop Update loop
(Update). Selection-tracking initial writes (SelectedIndex /
SelectedItem) need the collection in its final shape first — WinUI
silently clamps selection against an empty collection. The strategy
shape is unchanged; descriptors using ItemsHost simply get the items
populated first, matching legacy MountListBox ordering.
DescriptorHandler.Children returns null when the strategy is
ItemsHost so V1HandlerAdapter doesn't double-dispatch.
- V1HandlerAdapter ItemsHost branches kept for hand-coded handlers
(none today) and updated for the IList<object> typing.
Keyed-reconcile path (originally planned as part of G-prep for Batch G2)
deferred: typed templated lists need ReactorListState + KeyedListDiff
integration plus a Reconciler.BindKeyedItemsSource helper, which is
substantial engine work better landed alongside its consumer rather
than blind.
V1 ON: 511 ok / 0 failures
V1 OFF: 511 ok / 0 failures
* spec(047): Phase 3-final Batch G1 — flat ItemsHost ports (ListBox, ComboBox, RadioButtons)
Migrate the three flat-Items descriptors off the .OneWay<string[]>
escape-hatch and onto the ItemsHost child strategy delivered by G-prep.
The engine now dispatches ItemsHost inline between RentControl and the
prop loop, so SelectedIndex's initial write lands against a populated
collection (no more silent clamp-to-minus-one against empty Items).
Shipped:
- ListBoxDescriptor: ItemsHost flat — string[] -> IReadOnlyList<object>
via array reference covariance; ListBoxItemsEqual helper removed.
- ComboBoxDescriptor: ItemsHost flat — string items OR Element items
(ItemElements wins when non-null); the setter escape-hatch the prior
port required is no longer needed for the Items population path.
- RadioButtonsDescriptor: ItemsHost flat — same string[] projection;
RadioButtonsItemsEqual helper removed.
- Three new TAP fixtures (Desc_<Name>_Items_*) exercise the dispatch
ordering: initial SelectedIndex honored against populated items, no
mount-time echo, clear-to-empty + repopulate cycle, same-ref idempotent
short-circuit.
Carved (none): all three controls fit the flat-IList<object> shape;
typed templated lists go through G2.
V1 ON: 534 ok / 0 failures
V1 OFF: 534 ok / 0 failures
* spec(047): Phase 3-final — §14 progress note + tracker close-out
Documents the eight Phase 3-final batches (A engine shapes; B Frame /
RichTextBlock / NumberBox; C CalendarView via CollectionDiffControlled;
D Button-family Flyout via OneWayBridged + CreateFlyoutForDescriptor;
E Panel per-child attached props + WrapGrid; F Image events / Path.Data
/ InfoBar.ActionButton; G-prep ItemsHost IList<object> typing +
descriptor-side ordering fix; G1 flat ItemsHost ports for ListBox /
ComboBox / RadioButtons) and the deliberate carve-outs to Phase 4
(Expander.HeaderTemplate, RelativePanel per-child attached,
TeachingTip.Target, Path.PathDataString, NumberBox coercion, and the
G2/G3 templated lists which need a new TemplatedItems strategy +
spec-042 keyed-reconcile integration). Tracker marks the Batch
3-followup line as addressed via Phase 3-final Batches B and C.
ARM64 stable-AC re-capture on LAPTOP-4MEP83VI remains deferred for the
§14 ratification gate (unchanged from Phase 3 advisory state).
* spec(047): Phase 3-final — replace double == checks in Phase 3-final fixtures with epsilon
Silences github-code-quality bot comments on PR #436. The assertions read
back literal values we just wrote (FontSize, NumberBox Value / Minimum /
Maximum / SmallChange / LargeChange) so direct == was correct in practice,
but tolerance comparisons (Math.Abs(x - literal) < 1e-9) keep the bot
quiet for human reviewers and follow the convention CodeQL expects.
V1 ON: 534 ok / 0 failures
V1 OFF: 534 ok / 0 failures
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 9966902 commit 522c940
36 files changed
Lines changed: 3360 additions & 389 deletions
File tree
- docs/specs
- tasks
- src/Reactor/Core
- V1Protocol
- Descriptor
- Descriptors
- Handlers
- tests
- Reactor.AppTests.Host/SelfTest
- Fixtures
- Reactor.Tests/Spec047/V1Protocol
- Ports
- perf_bench/PerfBench.ControlModel/Variants
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1376 | 1376 | | |
1377 | 1377 | | |
1378 | 1378 | | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
1379 | 1401 | | |
1380 | 1402 | | |
1381 | 1403 | | |
| |||
Lines changed: 87 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
751 | 751 | | |
752 | 752 | | |
753 | 753 | | |
754 | | - | |
755 | | - | |
756 | | - | |
757 | | - | |
758 | | - | |
759 | | - | |
760 | | - | |
761 | | - | |
762 | | - | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
763 | 841 | | |
764 | 842 | | |
765 | 843 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
285 | 285 | | |
286 | 286 | | |
287 | 287 | | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | | - | |
300 | | - | |
301 | | - | |
302 | | - | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
319 | | - | |
320 | | - | |
321 | | - | |
322 | | - | |
323 | | - | |
324 | | - | |
325 | | - | |
326 | | - | |
327 | | - | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
328 | 293 | | |
329 | 294 | | |
330 | 295 | | |
| |||
618 | 583 | | |
619 | 584 | | |
620 | 585 | | |
621 | | - | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
622 | 590 | | |
623 | 591 | | |
624 | 592 | | |
625 | 593 | | |
626 | 594 | | |
627 | 595 | | |
628 | | - | |
| 596 | + | |
629 | 597 | | |
630 | 598 | | |
631 | 599 | | |
632 | 600 | | |
633 | 601 | | |
634 | 602 | | |
635 | | - | |
| 603 | + | |
636 | 604 | | |
637 | 605 | | |
638 | 606 | | |
| |||
646 | 614 | | |
647 | 615 | | |
648 | 616 | | |
649 | | - | |
| 617 | + | |
650 | 618 | | |
651 | 619 | | |
652 | 620 | | |
| |||
659 | 627 | | |
660 | 628 | | |
661 | 629 | | |
662 | | - | |
| 630 | + | |
663 | 631 | | |
664 | 632 | | |
665 | 633 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
658 | 658 | | |
659 | 659 | | |
660 | 660 | | |
661 | | - | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
662 | 665 | | |
663 | 666 | | |
664 | 667 | | |
| |||
3772 | 3775 | | |
3773 | 3776 | | |
3774 | 3777 | | |
3775 | | - | |
| 3778 | + | |
3776 | 3779 | | |
3777 | 3780 | | |
3778 | 3781 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4096 | 4096 | | |
4097 | 4097 | | |
4098 | 4098 | | |
| 4099 | + | |
| 4100 | + | |
| 4101 | + | |
| 4102 | + | |
| 4103 | + | |
| 4104 | + | |
| 4105 | + | |
| 4106 | + | |
| 4107 | + | |
| 4108 | + | |
| 4109 | + | |
| 4110 | + | |
4099 | 4111 | | |
4100 | 4112 | | |
4101 | 4113 | | |
| |||
0 commit comments