Commit ef7c4dd
fix(reconciler): templated ListView/GridView stale-closure refresh + feat(charting): rich Element labels with Canvas anchors (#160)
* fix(reconciler): templated ListView/GridView refresh realized containers on parent re-render; handle GridViewItem in refresh path
Two related correctness bugs in the templated list-view update path.
1. Stale viewBuilder closures on parent re-render
`UpdateTemplatedListView` / `UpdateTemplatedGridView` skipped refreshing
realized containers when the items reference was unchanged:
if (o.ItemCount != n.ItemCount)
lv.ItemsSource = …;
else if (!n.SameItemsAs(o)) // ReferenceEquals(Items, x.Items)
RefreshRealizedContainers(…); // skipped when items unchanged
The `SameItemsAs` shortcut assumed `viewBuilder` was a pure function of
`(item, index)`. In practice viewBuilder is a closure that legitimately
captures outer state — `count` from `UseState`, theme tokens, mode flags,
etc. With the shortcut in place, the surrounding controls (header, sibling
elements) refreshed on parent re-render but list rows stayed frozen at
their first-render closure values.
Repro: a list whose viewBuilder reads an outer `count`. Header text shows
`count: 6`; rows show `[count: 0]` indefinitely.
Fix: drop the shortcut. Always call `RefreshRealizedContainers` when the
item count is unchanged. Per-item cost is bounded by the leaf-level
`ShallowEquals` fast-path inside `Update`, so unchanged subtrees still
short-circuit cheaply. Removed the now-unused `SameItemsAs` abstract +
three implementations and their unit-test coverage.
2. GridView containers were silently dropped from refresh
`RefreshRealizedContainers` cast `ContainerFromIndex(i) as ListViewItem`.
For `GridView`, the container is a `GridViewItem`, so the cast returned
`null` and every iteration `continue`d — refresh was a no-op for GridView.
Both `ListViewItem` and `GridViewItem` derive from `SelectorItem`; cast
to that instead so both controls share the refresh path.
This bug never surfaced because (a) item-count changes hit the
`ItemsSource =` reset path that bypasses `RefreshRealizedContainers`
entirely, and (b) the now-removed `SameItemsAs` shortcut prevented the
cast from being exercised on most renders. Discovered by the new
GridView fixture below.
## Tests
- New selftest fixtures (Reactor.AppTests.Host/SelfTest/Fixtures/
TemplatedListHighlightTests.cs):
- TemplatedListView_ViewBuilderClosureRefreshes — proves rows reflect
a closure-captured `count` after a sibling state change with
stable items reference.
- TemplatedGridView_ViewBuilderClosureRefreshes — same contract for
GridView, also exercises the SelectorItem cast fix.
- Removed three unit-test assertions on the now-deleted `SameItemsAs`
method (MoreCoverageTests2.cs).
- Full suite: 6797 unit, 652 selftest, all passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(charting): rich Element labels for axis ticks and pie slices; Canvas anchor support
Adds three new chart customization APIs that accept a Reactor Element instead
of a string, plus the Canvas anchor primitive they require.
## New chart APIs
- ChartElement<T>.XTickLabelView(Func<double, Element>) — replace the built-in
numeric X-axis tick label with a caller-supplied Element. Anchored
horizontally centered on the tick mark.
- ChartElement<T>.YTickLabelView(Func<double, Element>) — same for the Y axis.
Right-anchored to the axis edge, vertically centered on the tick.
- PieChartElement<T>.LabelView(Func<T, PieSliceLayout, Element>) — replace the
built-in text slice label. Centered on the slice centroid; the original
string LabelAccessor remains the source of truth for accessibility, so
screen-reader summaries keep working.
PieSliceLayout exposes Index, Value, Fraction, CentroidX/Y, StartAngle/
EndAngle (radians, clockwise from 12 o'clock), Inner/OuterRadius, and the
resolved palette Color, so label authors can render shape/color/percent
without re-deriving slice geometry.
All Element labels are rendered with IsHitTestVisible=false and
AccessibilityView.Raw so they don't pollute the UIA tree — the chart's
existing IChartAccessibilityData remains the canonical accessible
representation.
D3Axes() picks up two new optional parameters (xTickLabel/yTickLabel) that
swap the built-in TextBlock for the supplied factory; behavior is unchanged
when both are null.
## Canvas anchor positioning
Rich labels need to position centered on a tick or centroid without knowing
their rendered size at construction time. The Canvas attached property gained
two anchor knobs:
- Canvas(left, top, anchorX, anchorY) — anchorX/Y are 0..1 fractions of the
element's rendered size. 0,0 = top-left (legacy), 0.5,0.5 = centered on
(left, top), 1,1 = bottom-right.
- CenterAt(x, y) — sugar for Canvas(x, y, 0.5, 0.5).
Implementation: ApplyCanvasPosition() in Reconciler.cs falls back to plain
Canvas.SetLeft/SetTop when anchor is (0,0) — zero overhead for existing
callsites. When an anchor is set, per-FE state is held in a
ConditionalWeakTable, the element subscribes to Loaded + SizeChanged once,
and Canvas.Left/Top are recomputed as `target - anchor * ActualWidth/Height`
on every layout pass. Updates that swap the anchor reuse the existing
subscriptions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): add reactor-charts skill covering the chart DSL and the new *View extension points
skills/charts.md is targeted at AI agents helping with chart customization. It
covers the four chart factories (LineChart, BarChart, AreaChart, PieChart)
briefly, then concentrates on what isn't obvious from the public guide: when
the string-label APIs aren't enough and the user needs to reach for
PieChartElement<T>.LabelView(Func<T, PieSliceLayout, Element>)
ChartElement<T>.XTickLabelView(Func<double, Element>)
ChartElement<T>.YTickLabelView(Func<double, Element>)
added in this same PR. Documents the PieSliceLayout fields, the auto-applied
defensive defaults (IsHitTestVisible=false, AccessibilityView.Raw), and the
a11y rule that callers MUST keep the string LabelAccessor / DataLabel set so
the chart's IChartAccessibilityData stays the canonical UIA description even
when the visual is replaced. Also briefly cross-references the underlying
Canvas anchor primitive (.CenterAt, .Canvas(left, top, anchorX, anchorY)).
Format mirrors the existing skills (perf-tips.md, input.md, etc.) — concise,
scannable, with a "when to reach for it / when not to" framing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): expand charts skill with chart-choice, D3, label, and a11y guidance; link from SKILL.md
skills/charts.md now covers, in order: (1) choosing the right chart for the
data, (2) the DSL and *View extension points, (3) D3 scales/axes pitfalls,
(4) direct labeling vs legend vs tooltip, (5) a11y beyond the framework
defaults, (6) the most-cited visualization mistakes to refuse to ship.
Drawn from the standard visualization literature — Cleveland & McGill's
perceptual ranking (1984), Tufte's data-ink ratio and chartjunk, Few's
practitioner playbook, Cairo's How Charts Lie pitfalls chapter, Wilke's
Fundamentals of Data Visualization, and the W3C WAI / Tenon accessibility
guidance for charts. Each rule is a one-line heuristic an AI agent can scan
quickly. The table at the top maps "use when / avoid when" for the four
chart types so the agent has an immediate answer when a user asks for the
wrong chart.
Specifically called out:
- Pie chart guard rails (4 conjoint conditions; default to sorted bar).
- Truncated baselines on bar charts (always anchor at zero).
- Dual y-axes (don't; use small multiples or index to 100).
- Color-as-sole-channel (WCAG 1.4.1; pair with shape/pattern/label).
- Direct labeling beats legends when feasible — exactly what the new *View
methods enable, so the skill ties the technique to the framework feature.
SKILL.md sub-skills table gains a charts row so the root skill's index points
agents at this. Packaging is automatic — release.yml's "Assemble skill kit"
step does Copy-Item -Recurse skills $stage, so any new skills/*.md is
included in reactor-skill-kit-<version>.zip without further changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): cover donut, TreeChart, ForceGraph in charts skill
The previous version of skills/charts.md only listed the four "primary"
chart factories (Line/Bar/Area/Pie) and missed three that ship in the
framework:
- Donut: actually a parameter on PieChart (`.InnerRadius(r)` for r > 0),
not a separate factory. Worth calling out because the inner-hole label
pattern is a common ask and `InnerRadius` is non-obvious from the
factory signature.
- TreeChart (`Charts.Tree.cs`): top-level factory for hierarchical data —
org charts, file trees, taxonomies. Has its own ChartElement, fluent
surface, and accessibility data.
- ForceGraph (`Charts.Tree.cs`): top-level factory for relationship
networks. Same shape.
Also added a paragraph noting the D3 layout primitives that aren't yet
wrapped in a factory — Sankey, Treemap, Cluster, Stratify — so the agent
knows where to look when a user asks for one of these and can correctly
suggest "compose from D3 primitives, or file a factory request" rather
than claim it doesn't exist.
Frontmatter description and SKILL.md table row updated to mention the
broader catalog so the chart-shape question routes here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(devtools,docs): unbreak preview-capture pipeline after security hardening
Two regressions from #102 (security hardening) compounded to wedge
`mur docs compile` for any topic with screenshots — Phase 3 timed out
waiting for the capture port and the app silently exited.
PreviewCaptureServer.Start: the TASK-026 TOCTOU fix kept a TcpListener
bound on the loopback port until HttpListener.Start() succeeded. The
kernel only lets one socket own the port, so HttpListener.Start() threw
SocketException; the exception escaped OnLaunched, was logged to
NullLogger.Instance (the default _logger before any logger is wired),
and the process exited. Stop the placeholder TcpListener before binding
HttpListener — the resulting Stop->Start TOCTOU is microseconds wide on
loopback and not a meaningful local-attack surface.
PreviewCaptureServer.ServeFrame: the TASK-025 idle-stop optimization
queued Start() and Stop() on the dispatcher for every /frame request.
A serial-polling client (the docs CLI) toggled _activeReaders 0->1->0
fast enough that Start and Stop landed on the dispatcher back-to-back
and cancelled out before any tick fired — the timer never produced a
single frame. Drop the idle-stop; keep lazy-start. The CPU cost of a
5-10 fps PrintWindow loop on an idle preview window is negligible, and
the alternative is a request-overlap dance the docs pipeline can't do.
ScreenshotCapture (CLI): the bearer-token plumbing added on the server
side (TASK-018) was never wired into the docs-pipeline client. Read
CAPTURE_TOKEN= from app stdout alongside CAPTURE_PORT= and send
Authorization: Bearer ... on every /preview and /frame request. Also
poll /frame until a non-empty body lands (capture timer starts lazily,
so the first call returns 204) and warm the timer before the manifest
loop so the first screenshot doesn't pay the startup latency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(gallery): disable horizontal scroll on sample detail page
Without HorizontalScrollBarVisibility=Disabled, the no-wrap source-code
TextBlock propagates its full unwrapped width up through both
ScrollViewers and inflates the page-level StackPanel past the visible
viewport. That stretched the chart-card Border with it, and HAlign=Center
then placed each sample's chart at the middle of the inflated width —
visually far off to the right of the window. ScrollMode.Disabled only
suppresses the gesture; the visibility flag is what clamps measure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(charting): cover the new Element-label extension points
Adds a "Custom Label Elements" section to the charting guide, framed as
an advanced opt-in: most charts only need the string label APIs
(AxisLabel, LabelAccessor, DataLabel); reach for *View when plain text
isn't enough — icon-plus-text ticks, multi-line labels with mixed
typography, or rendering a slice's percent inside the slice itself.
Two focused subsections with their own snippets:
- PieChartElement<T>.LabelView(Func<T, PieSliceLayout, Element>) —
PieLabelViewDemo renders the slice percentage inside the slice. The
string LabelAccessor is still passed so screen readers describe the
slice; the chart's IChartAccessibilityData stays canonical even when
the visual is replaced.
- ChartElement<T>.XTickLabelView(Func<double, Element>) +
YTickLabelView — AxisTickViewDemo renders month-name X-axis labels
with a "month" caption per tick.
Closing paragraph documents the auto-applied defensive defaults
(IsHitTestVisible=false, AccessibilityView=Raw) so callers don't have
to repeat them, and reminds them that the string label is required.
Generated docs/guide/charting.md and screenshots via `mur docs compile
--topic charting`. Only commits the three new images
(pie-label-view, axis-tick-view, accessible-chart); the other six
charting screenshots regenerate byte-different but visually identical
on every capture (PrintWindow + JPEG re-encode noise) and are reverted
to keep history clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* review: address PR #160 CR feedback — palette consistency, OnMount chaining, virtualized refresh, chart unit tests
Five CR concerns fixed; two declined. Pre-OSS so we keep moving — no compat
shims kept just to placate the reviewer.
## Fixed
1. **OnMount clobbering on caller-supplied chart labels** (Charts.cs:608,
D3Charts.cs:395, D3Charts.cs:411). The chart code wrapped author-supplied
`Element`s in `.OnMount(static fe => fe.IsHitTestVisible = false)` to
stamp the defensive-default. ElementModifiers stores a single
`OnMountAction`, so plain `.OnMount(…)` overwrote any mount hook the
caller already had. New `OnMountAdd` extension composes: existing action
runs first, then the new one. All three sites now use OnMountAdd.
2. **Palette inconsistency between rendered slices and PieSliceLayout.Color**
(Charts.cs RenderLabelViews + D3Charts.cs D3Pie). The label path resolved
`_colorPalette ?? D3Color.Category10`, but D3Pie hard-coded
`D3Charts.Palette` (also Category10). When a caller used SetColors(...),
the labels saw the custom palette while the slices kept the static one,
so PieSliceLayout.Color (which label authors lean on) didn't match what
was actually rendered. D3Pie now accepts an optional palette parameter
and PieChartElement passes the same palette it gave RenderLabelViews.
3. **SetColors(empty) stored an empty palette** (Charts.cs:467). Caller
could `chart.SetColors()` (no args) and every downstream consumer would
mod-by-zero on `palette[i % palette.Count]`. Empty input now clears the
override so the default palette applies; documented why we don't store
"no colors" as a meaningful state.
4. **RefreshRealizedContainers was O(ItemCount) on virtualized lists**
(Reconciler.Update.cs:2510). The previous loop did
`for i in 0..ItemCount: ContainerFromIndex(i)`, which on a 10k-item
virtualized ListView meant 10k cross-WinRT lookups per parent re-render
and discarded most as null. Now iterates `ItemsPanelRoot.Children`
directly — that IS the realized set, so cost is O(realized) ≈ viewport
size. Container index is resolved via `IndexFromContainer` instead of
driving the loop with a guessed index. Snapshots Children before the
inner Update calls (which can mount new controls and would throw on
live-collection enumeration).
5. **No unit-test coverage for the new *View APIs / PieSliceLayout / SetColors**
(Charts.cs:142, 491, 603 — three duplicate review comments). Added six
tests in ChartsBuilderTests.cs covering builder fluency for
`XTickLabelView` / `YTickLabelView` / `LabelView`, `PieSliceLayout` field
surface, and the empty-palette behavior. Visual end-to-end coverage
stays in the selftest fixtures (Charts construct WinRT brushes during
ToElement, which can't run on the unit-test thread).
## Declined
- **Restore SameItemsAs as an obsolete shim** — the framework isn't shipped
yet, the protected/abstract method has no external derivers, and an
Obsolete shim would just be dead weight. Hard-removed.
- **AnchorX/AnchorY look like a no-op** — stale review. ApplyCanvasPosition
reads them and is wired into both Mount (Reconciler.Mount.cs:1025) and
Update (Reconciler.Update.cs:1202).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* samples(charting): two demos for the new *View extension points
- WeeklyForecastSample: 14-day temperature forecast where both axis tick
labels are Reactor Elements via XTickLabelView / YTickLabelView. Each X
tick is a stacked '№ / day' label; each Y tick pairs the temperature
with a color swatch that warms with the value.
- BrowserSharePieSample: PieChart.LabelView demo — slice labels render the
browser logo + percentage centered on each slice's centroid.
Both samples ride alongside the existing chart gallery entries and
register through SampleRegistry. GalleryHelpers.GallerySample.IconName
becomes virtual so the new samples can override it where needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(reconciler,devtools): structural AccessibilityModifiers compare; sprite-leak in highlight overlay
Two fixes that surfaced together while investigating "the entire chart
lights up as modified" when only one descendant property changed in
step-08 (PieChart with LabelView rendering Component<ListItem,ItemProps>).
## 1. ModifiersEqual: AccessibilityModifiers must compare by value (#167 follow-up)
`Element.ModifiersEqual` was checking the `Accessibility` slot via
`ReferenceEquals`. Every fluent helper (`.AccessibilityView`,
`.LiveRegion`, `.ItemStatus`, `.HelpText`, …) routes through
`ModifyA11y`, which allocates a fresh `AccessibilityModifiers` per
call — so the reference is never equal across renders even when
the values are identical.
Cascaded into:
- the Update fast-path skip failing for any element with an
accessibility modifier (charts, axis ticks, slice labels), and
- the reconcile-highlight overlay flagging those elements as
"modified" every render even though `OwnPropsEqual` correctly
returned true (Components/Func/Memo are pure wrappers).
Fixed by comparing the record structurally (it's all scalar/string
fields). Added 5 regression tests in `ReconcilerCorrectnessTests`.
## 2. ReconcileHighlightOverlay: sprite cleanup leak
The fade animation cleanup in `Show()` removed sprites whose
`Opacity <= 0.001f`. But Composition keyframe animations drive the
*rendered* value without writing back to the static property —
`sprite.Opacity` keeps reading 0.17 even after the fade completes.
The predicate was effectively always false, so sprites accumulated
in `_container.Children` for the life of the session.
Two consequences:
- Memory leak (one container, growing without bound).
- Visual stacking: at 80ms flush cadence and 600ms fade, ~7 sprite
generations stacked at the same target position. Repeated 17%-yellow
composition over the same pixel approaches saturated yellow film
over the slice color underneath, which read as the chart "fading".
Fixed by tracking each batch's sprites in a per-flush `List<SpriteVisual>`
and removing exactly those when `batch.Completed` fires — no
introspection of an unreliable property.
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 6d9fd00 commit ef7c4dd
32 files changed
Lines changed: 1359 additions & 89 deletions
File tree
- docs
- _pipeline
- apps/charting
- templates
- guide
- images/charting
- samples/ReactorCharting.Gallery
- Icons
- Samples
- skills
- src
- Reactor.Cli/Docs
- Reactor
- Charting
- Core
- Elements
- Hosting
- tests
- Reactor.AppTests.Host/SelfTest
- Fixtures
- Reactor.Tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
193 | 193 | | |
194 | 194 | | |
195 | 195 | | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
196 | 263 | | |
197 | 264 | | |
198 | 265 | | |
| |||
251 | 318 | | |
252 | 319 | | |
253 | 320 | | |
| 321 | + | |
| 322 | + | |
254 | 323 | | |
255 | 324 | | |
256 | 325 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
38 | 48 | | |
39 | 49 | | |
40 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
126 | 172 | | |
127 | 173 | | |
128 | 174 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
272 | 272 | | |
273 | 273 | | |
274 | 274 | | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 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 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
275 | 341 | | |
276 | 342 | | |
277 | 343 | | |
| |||
Loading
Loading
Loading
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
Loading
0 commit comments