perf(map): raise tile concurrency and cancel superseded vector tile loads - #387
perf(map): raise tile concurrency and cancel superseded vector tile loads#387NearlCrews wants to merge 3 commits into
Conversation
|
According to Openlayers documentation |
|
Good catch, confirmed against the OL 10.7 docs: I'll split this: land the |
|
Trimmed to the safe parts and rebased onto master: kept the |
9228386 to
df0f5e1
Compare
|
Thanks for trimming this down — splitting off the deprecated raster My Claude asked your Claude for this: # Verification — PR #387 (vector tile abort + maxTilesLoading)
Context: this PR replaces OpenLayers' built-in VectorTile load path with a
custom `tileLoadFunction` (`createAbortableVectorTileLoader` in
`src/app/modules/map/ol/lib/charts/tile-loader-abort.ts`) that cancels
in-flight tile loads from superseded zoom levels, and bumps
`maxTilesLoading` from 16 to 32 in `map.component.ts`.
The current "Tested" evidence is `tsc --noEmit` + `build:web` — compile only.
There are two gaps to close: (1) a unit test proving the abort bookkeeping,
and (2) runtime proof the vector charts still render and requests cancel.
---
## Part 1 — Add a vitest spec for the abort bookkeeping
FSK runs vitest with co-located `*.spec.ts`. Add
`src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts` proving the
per-zoom supersede logic. The pure helpers (`bucketForZoom`,
`releaseController`) are the cleanest seam — export them from the module
(keep `createAbortableVectorTileLoader` as the public entry) so they can be
tested directly without mocking the whole OL VectorTile.
Cases to cover:
- **Same-zoom reuse:** two calls at zoom Z return the *same* controller set;
no controllers are aborted.
- **Supersede aborts others:** with controllers held under zoom Z1, a call
for zoom Z2 aborts every Z1 controller (assert `.signal.aborted === true`)
and removes the Z1 bucket from the map.
- **Release cleanup:** removing the last controller for a zoom deletes that
zoom's bucket entirely; removing one of several leaves the rest intact.
- **Release is zoom-safe:** releasing a controller under a zoom that's
already been superseded/deleted is a no-op, not a throw.
Run `ng test` (or the vitest invocation this repo uses) and confirm green.
---
## Part 2 — Runtime verification
### Setup
1. Check out the PR branch (`perf/chart-tile-throughput`) and `ng serve`.
2. Point FSK at a Signal K server with a **vector (MVT) chart source**
served over **HTTP/1.1** (the connection-cap condition this PR targets —
confirm in DevTools the tile responses are h1, not h2).
3. Open FSK in Chrome → DevTools → Network, filter to the tile host,
enable "Disable cache".
### Checks (capture an artifact for each)
A. **Vector charts still render correctly.**
Load the vector chart; pan and zoom across a few levels. Confirm features
draw with no missing/blank tiles and no console errors.
→ Artifact: screenshot of the rendered vector chart + clean console.
B. **Superseded requests actually cancel.**
Zoom out across 2–3 levels rapidly (faster than tiles finish). In the
Network panel, confirm tile requests from the abandoned zoom level(s)
show status **(canceled)**.
→ Artifact: Network-waterfall screenshot showing (canceled) rows for the
superseded zoom, plus the in-flight count for the visible level.
C. **No regression in the steady state.**
Zoom to a level and let it fully settle. Confirm every tile reaches
LOADED and nothing is stuck in (canceled) or error.
→ Artifact: waterfall of a clean settle (all 200/LOADED).
D. **maxTilesLoading effect (optional).**
On the same HTTP/1.1 source, compare time-to-visually-settle after a
zoom-out burst on this branch vs. master, same hardware.
→ Artifact: before/after settle time (or a short screen capture).
### Edge cases to spot-check
- Cross-origin source: does plain `fetch(src)` still load (CORS /
credentials) the same as OL's default loader did? No CORS errors for a
remote chart host.
- Rapid pinch-zoom through intermediate levels: the final level renders and
you don't end up with a level whose tiles were all aborted and never
re-requested.
---
## Output
- Confirm the new spec passes (`ng test`).
- Post the runtime artifacts on PR #387, one line each on what they show.
- If any check fails, note which and the console/network detail. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds an abortable vector-tile loader with per-zoom controller tracking. Superseded requests are aborted, tile states update after fetch results, and chart layers cancel pending loads during destruction. The map allows up to 32 concurrent tile loads. ChangesAbortable Vector Tile Loading
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant MapComponent
participant VectorChartLayerComponent
participant VectorTileSource
participant TileLoader
participant Fetch
MapComponent->>MapComponent: Create OpenLayers Map with maxTilesLoading=32
VectorChartLayerComponent->>TileLoader: Create abortable tile loader
VectorChartLayerComponent->>VectorTileSource: Set tileLoadFunction
VectorTileSource->>TileLoader: Load vector tile
TileLoader->>Fetch: Fetch tile data with AbortSignal
Fetch-->>TileLoader: Return array-buffer data
TileLoader-->>VectorTileSource: Set features and tile state
VectorChartLayerComponent->>TileLoader: Abort pending loads on destroy
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Both parts are in. Part 1: unit spec. Added
Green at Part 2: runtime. I drove the actual Results (request log, 63 tile requests):
This is a synthetic harness exercising the real loader code path, not FSK driven by hand against a Signal K vector source. If you would rather see it against a live SK chart source, or want the render and waterfall screenshots and the harness script, say the word and I will post them. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts (1)
12-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd specs for
createAbortableVectorTileLoader().These tests only cover the helper map/set bookkeeping. The new behavior that actually ships here—fetch cancellation,
AbortErrorhandling, tile state transitions, and cleanup after settle—is still untested, so the main regression surface from this PR would slip through.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts` around lines 12 - 120, The current spec only covers bucket bookkeeping in bucketForZoom and releaseController, but not the actual loader behavior introduced by createAbortableVectorTileLoader(). Add tests around createAbortableVectorTileLoader that verify an in-flight fetch is canceled via AbortController, AbortError is handled without failing the tile, tile state transitions happen correctly on success/failure, and cleanup occurs after the request settles. Use the existing helper patterns in the spec to locate and exercise the loader entry points directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/modules/map/ol/lib/charts/tile-loader-abort.ts`:
- Around line 52-58: Expose a teardown hook from createAbortableVectorTileLoader
so callers can abort all pending controllers held in the pending map. Update the
loader factory and the createAbortableVectorTileLoader return type to include a
cleanup/teardown function, and use it from
VectorChartLayerComponent.ngOnDestroy() to cancel any still-running tile fetches
before the layer is removed.
- Around line 80-86: The abort handling in tile-loader-abort should put
superseded tiles into a terminal state instead of leaving them stuck in LOADING.
Update the abort path around the tile load promise and the tile state logic in
tile-loader-abort so AbortError is treated as a terminal outcome and the tile is
set to ERROR via tile.setState, while keeping EMPTY reserved for intentionally
blank/disposed tiles. Use the existing tile.setState, TileState.ERROR, and abort
catch/finally flow to locate the fix.
---
Nitpick comments:
In `@src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts`:
- Around line 12-120: The current spec only covers bucket bookkeeping in
bucketForZoom and releaseController, but not the actual loader behavior
introduced by createAbortableVectorTileLoader(). Add tests around
createAbortableVectorTileLoader that verify an in-flight fetch is canceled via
AbortController, AbortError is handled without failing the tile, tile state
transitions happen correctly on success/failure, and cleanup occurs after the
request settles. Use the existing helper patterns in the spec to locate and
exercise the loader entry points directly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b7783283-a22a-400f-9956-6672af3a1421
📒 Files selected for processing (4)
src/app/modules/map/ol/lib/charts/layer-vector-chart.component.tssrc/app/modules/map/ol/lib/charts/tile-loader-abort.spec.tssrc/app/modules/map/ol/lib/charts/tile-loader-abort.tssrc/app/modules/map/ol/lib/map.component.ts
|
This is exactly the evidence I was after — thank you for the thorough turnaround. The vitest cases nail the supersede bookkeeping, and the headless run over a real HTTP/1.1 + latency path is a clean way to prove the two things I couldn't take on faith from a green build: vector charts still render, and superseded loads actually cancel (the 15 CodeRabbit has since flagged two things I'd treat as substantive rather than style, and I'd like both addressed before merge:
Its third note (test Since you're on Claude Code: CodeRabbit drops a "🤖 Prompt for AI Agents" block under each finding, plus a combined "Prompt for all review comments with AI agents" at the end of its review — those are written to paste straight into your agent, so you don't have to translate the comments by hand. Get those two items sorted and I think this is ready to land. |
4a814f8 to
833a4b6
Compare
|
@coderabbitai reviews remaining |
|
Your plan includes PR reviews subject to rate limits. More reviews will be available in 17 minutes. |
|
@NearlCrews For your AI Agent: # PR #387 — finish the two open CodeRabbit items
You are working on branch `perf/chart-tile-throughput` in
`SignalK/freeboard-sk`, currently at head `833a4b6`.
## State of play (verify before you start)
The 2026-07-27 push was a **rebase onto master with no source change**.
`src/app/modules/map/ol/lib/charts/tile-loader-abort.ts` is byte-identical to
what CodeRabbit reviewed at `4a814f8`, and `VectorChartLayerComponent.ngOnDestroy()`
is unchanged. Both actionable CodeRabbit findings from the 2026-06-27 review are
still open. Confirm this yourself against the current working tree before
concluding anything is already done — if the fixes exist on a different local
branch, the wrong branch was pushed.
Do **not** post `@coderabbitai review` to re-trigger a review. The rebase carried
no source change, so a re-review would re-post the identical findings and consume
a rate-limited slot for nothing. Push the fixes first; the push itself triggers
the review.
## Task 1 (merge gate) — give aborted tiles a terminal state
In `tile-loader-abort.ts`, the catch block is currently:
```ts
.catch((err) => {
if (err?.name !== 'AbortError') {
tile.setState(TileState.ERROR);
}
})
```
On abort the tile is left in `TileState.LOADING` permanently. Move it to a
terminal state (`TileState.ERROR`; reserve `EMPTY` for intentionally blank or
disposed tiles).
**Why this is the gate and not a tidiness nit.** OpenLayers' tile queue counts
tiles in `LOADING` against the `maxTilesLoading` budget. If every supersede
strands a tile in `LOADING`, the budget monotonically erodes over a session of
repeated zooms — which would undermine the exact concurrency headroom this PR
raises from 16 to 32. The existing runtime harness cannot detect this: it runs a
single zoom sequence and checks the final settle, so a slow leak across many
cycles is invisible to it.
**Verify empirically, don't just reason about it.** Instrument the map's
tiles-loading count (OL exposes the tile queue's loading count; find the accessor
in the installed `ol` version rather than assuming an API shape) and run several
dozen rapid zoom cycles. Acceptance: the count returns to zero when the map
settles, and does not ratchet upward across cycles. Report the before/after
numbers — if the count was already returning to zero on the current code, say so
explicitly, because that would mean OL reclaims these tiles some other way and
the severity here is lower than stated.
## Task 2 — teardown on layer destroy
`createAbortableVectorTileLoader()` currently returns only the loader function,
so nothing aborts the controllers still held in the `pending` map when a layer
goes away. Expose a cleanup/teardown alongside the loader and call it from
`VectorChartLayerComponent.ngOnDestroy()` (currently lines ~51-57, which only do
`removeLayer` + `render`) so in-flight fetches are cancelled before the layer is
removed. This compounds with Task 1: a destroyed layer's tiles are stranded the
same way.
## Task 3 (optional, closes the CodeRabbit nitpick)
`tile-loader-abort.spec.ts` covers `bucketForZoom` and `releaseController` well —
that part is done and is a genuine regression guard. It does not cover
`createAbortableVectorTileLoader()` itself, which is where both fixes above land.
Add cases asserting: an in-flight fetch is cancelled via its `AbortController`,
an `AbortError` drives the tile to a terminal state, success drives it to
`LOADED`, and the controller is released from `pending` after settle. A slice of
the existing runtime harness promoted into a committed test would cover this.
## Definition of done
- Both fixes pushed as commits on this branch (incremental commits are fine).
- `ng test` green.
- The zoom-cycle measurement from Task 1 reported in a PR comment, with numbers.
- If you conclude any finding is not valid against the current code, skip it and
say why rather than changing code to satisfy it.The two items above are what's between this and merge — everything else is in good shape (CI fully green including the slow armv7 Cerbo job, and the abort-bookkeeping spec from the last round does its job). One heads-up: the CodeRabbit check on the current head reads green, but that's a rate-limited non-review, not an approval — worth knowing so it doesn't read as all-clear. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== branch and relevant commits =='
git status --short
git branch --show-current
git rev-parse --short HEAD
git diff --no-ext-diff 4a814f8..HEAD -- \
src/app/modules/map/ol/lib/charts/tile-loader-abort.ts \
src/app/modules/map/ol/lib/charts/layer-vector-chart.component.ts || true
echo '== source outlines =='
ast-grep outline src/app/modules/map/ol/lib/charts/tile-loader-abort.ts --items all
ast-grep outline src/app/modules/map/ol/lib/charts/layer-vector-chart.component.ts --items all
ast-grep outline src/app/modules/map/ol/lib/map.component.ts --items all
echo '== targeted source =='
cat -n src/app/modules/map/ol/lib/charts/tile-loader-abort.ts
cat -n src/app/modules/map/ol/lib/charts/layer-vector-chart.component.ts
cat -n src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts
sed -n '1,220p' package.json
echo '== test config and OpenLayers dependency locations =='
fd -HI '^(vite|vitest|angular)\.config\..*$|^package\.json$|^TileQueue\.(d\.ts|js)$' . \
-E .git -E dist -E build -E node_modules/.cache || true
rg -n -C 3 --glob 'package.json' --glob '*.{ts,js,mts,cts}' \
'(vitest|ng test|TileQueue|tilesLoading|tilesLoading_|getTilesLoading)' \
. -g '!node_modules/.cache/**' -g '!dist/**' -g '!build/**' || true
echo '== installed OpenLayers tile queue declarations/source, if present =='
if [ -d node_modules/ol ]; then
rg -n -C 4 '(getTilesLoading|tilesLoading|tilesLoading_)' node_modules/ol \
-g 'TileQueue.*' -g 'Map.*' || true
fiLength of output: 46806 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== tracked performance/runtime harness and test configuration =='
fd -HI . dev-tools/perf-harness .github 2>/dev/null || true
ast-grep outline vitest-base.config.ts --items all 2>/dev/null || true
cat -n vitest-base.config.ts 2>/dev/null || true
find dev-tools/perf-harness -maxdepth 3 -type f -print 2>/dev/null | sort | while read -r file; do
echo "----- $file"
sed -n '1,260p' "$file"
done
echo '== OpenLayers queue API references in tracked code =='
rg -n -C 5 --glob '*.{ts,js,mts,cts}' \
'(getTileQueue\s*\(|getTilesLoading\s*\(|tilesLoading_)' \
src dev-tools .github || true
echo '== GitHub PR and commits =='
gh pr view 387 --repo SignalK/freeboard-sk \
--json number,headRefName,headRefOid,commits,files \
--jq '{number, headRefName, headRefOid, commits: [.commits[] | {oid: .oid, messageHeadline: .messageHeadline}], files: [.files[] | .path]}'
gh api repos/SignalK/freeboard-sk/commits/4a814f8 \
--jq '{sha, files: [.files[] | select(.filename == "src/app/modules/map/ol/lib/charts/tile-loader-abort.ts" or .filename == "src/app/modules/map/ol/lib/charts/layer-vector-chart.component.ts") | {filename, sha, status}]}' \
|| trueLength of output: 59304 🤖 web_search executed:
Length of output: 7381 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== current loader blob vs reviewed loader blob =='
printf 'current git blob: '
git hash-object src/app/modules/map/ol/lib/charts/tile-loader-abort.ts
echo 'reviewed GitHub blob: a4ed22a4fa54deb67fb952d5574a7ba0c3434ae0'
gh api repos/SignalK/freeboard-sk/git/blobs/a4ed22a4fa54deb67fb952d5574a7ba0c3434ae0 \
--jq '.content' | tr -d '\n' | base64 -d | sha1sum
echo '== OpenLayers v10.7.0 Map and TileQueue APIs =='
for path in src/ol/Map.js src/ol/TileQueue.js; do
echo "----- $path"
gh api "repos/openlayers/openlayers/contents/$path?ref=v10.7.0" \
--jq '.content' | tr -d '\n' | base64 -d \
| rg -n -C 5 '(getTileQueue|getTilesLoading|tileQueue_|loadMoreTiles|class TileQueue)'
doneLength of output: 1257 🏁 Script executed: #!/bin/bash
set -euo pipefail
for path in src/ol/Map.js src/ol/Map.d.ts src/ol/TileQueue.js src/ol/TileQueue.d.ts; do
echo "----- $path"
gh api "repos/openlayers/openlayers/contents/$path?ref=v10.7.0" \
--jq '.content' 2>/dev/null \
| tr -d '\n' \
| base64 -d 2>/dev/null \
| rg -n -C 5 '(getTileQueue|getTilesLoading|tileQueue_|loadMoreTiles|class TileQueue)' \
|| true
doneLength of output: 4069
OpenLayers 10.7 has
Failed to handle agent chat message. Please try again. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== relevant source outlines =='
ast-grep outline src/app/modules/map/ol/lib/charts/tile-loader-abort.ts --items all
ast-grep outline src/app/modules/map/ol/lib/charts/layer-vector-chart.component.ts --items all
ast-grep outline src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts --items all
echo '== current relevant source =='
cat -n src/app/modules/map/ol/lib/charts/tile-loader-abort.ts
cat -n src/app/modules/map/ol/lib/charts/layer-vector-chart.component.ts
cat -n src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts
echo '== loader factory definitions and call sites =='
rg -n -C 4 --glob '*.{ts,tsx}' \
'\bcreateAbortableVectorTileLoader\s*\(|\btileLoadFunction\s*:' \
src/app/modules/map/ol/libLength of output: 18225
The appropriate implementation is therefore to return a |
833a4b6 to
3243f7f
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts`:
- Around line 7-13: Update the tests in the tile-loader-abort spec to import and
exercise only the public createAbortableVectorTileLoader factory, removing
imports and direct suites for PendingByZoom, bucketForZoom, and
releaseController. Cover repeated and superseded zoom loads, verify fetch abort
signals and tile states, and validate teardown behavior through the factory’s
public contract.
In `@src/app/modules/map/ol/lib/charts/tile-loader-abort.ts`:
- Around line 82-83: Update the fetch chain in the tile-loading function around
fetch and r.arrayBuffer() to check Response.ok before reading the body,
rejecting non-success responses so they reach the existing catch path and set
TileState.ERROR. Add a factory-level test that verifies an HTTP 404 or 500
response produces the error tile state.
- Around line 90-91: Remove the explicit any cast in the VectorTileLoadFunction
callback and call setFeatures directly on the tile parameter, preserving the
existing features argument and behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7c56e183-482d-4e67-8688-b9abadeb1f12
📒 Files selected for processing (4)
src/app/modules/map/ol/lib/charts/layer-vector-chart.component.tssrc/app/modules/map/ol/lib/charts/tile-loader-abort.spec.tssrc/app/modules/map/ol/lib/charts/tile-loader-abort.tssrc/app/modules/map/ol/lib/map.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/modules/map/ol/lib/map.component.ts
3243f7f to
8cd4e74
Compare
…oads Raise OL maxTilesLoading from the default 16 to 32 so zoom-out tile bursts keep the browser's HTTP/1.1 connection pool fed. Add an abortable VectorTile tileLoadFunction that cancels in-flight loads from superseded zoom levels, freeing the connection pool for the now-visible level. Raster-source cancellation is intentionally left out: OL deprecates tileLoadFunction on raster (UrlTile) sources in favor of an ImageTile loader, which is a larger rework for a follow-up.
8cd4e74 to
38ba137
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts`:
- Around line 63-89: Update the fetchMock implementation in the successful
request test to preserve the Promise<Response> mock behavior without declaring
unused _src or _init parameters. Keep the existing successful response and fetch
call typing intact, and leave the surrounding tile-loading assertions unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ee5a792a-cc1e-4212-83e0-1feb98339510
📒 Files selected for processing (2)
src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.tssrc/app/modules/map/ol/lib/charts/tile-loader-abort.ts
|
Repeated rapid-zoom queue measurement is complete. I ran the production
The settled counts never ratcheted upward. I removed the temporary |
|
Closing because this branch targets code that has since changed, so the proposal is no longer current. |
What problem does this solve?
Rapid zooming on HTTP/1.1 vector chart sources leaves superseded requests consuming browser connection slots and OpenLayers tile-queue capacity. The visible zoom level can therefore wait behind tiles the user has already left.
How does it work?
The map raises
maxTilesLoadingfrom 16 to 32. Non-PMTilesVectorTilesources use an abortable loader grouped by zoom level, so a new zoom cancels requests from older levels. Every failed or canceled tile reaches the terminalERRORstate, non-success HTTP responses reject before decoding, completed controllers leave the bookkeeping map, and component teardown aborts remaining requests.Raster and PMTiles loading are unchanged.
What changes for the user?
Vector charts recover and settle more quickly after rapid zoom changes, especially when their tile server uses HTTP/1.1. There is no new control or visual change.
How was this tested?
npm run test:ci -- --include src/app/modules/map/ol/lib/charts/tile-loader-abort.spec.ts: 1 file and 5 tests passed, covering success, repeated same-zoom loads, supersession, teardown, HTTP errors, fetch errors, and cleanup.tscchecks pass locally.a916856e.Checklist
features/untouched; the user-facing change is described above instead.DEV-LESSONS-LEARNED.mdfor traps relevant to this change.npm run test:cipasses.