Skip to content

fix(parseMap): keep custom-aggregated channels when the saved field is null [sc-561439] - #328

Merged
padawannn merged 1 commit into
mainfrom
fix/sc-561439/custom-aggregation-null-color-field
Jul 30, 2026
Merged

fix(parseMap): keep custom-aggregated channels when the saved field is null [sc-561439]#328
padawannn merged 1 commit into
mainfrom
fix/sc-561439/custom-aggregation-null-color-field

Conversation

@padawannn

@padawannn padawannn commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

A spatial-index layer styled with a custom aggregation renders as a flat fixed color through fetchMap instead of its graduated palette. Builder shows the same map correctly, so the saved config is fine — the divergence is in this package.

Both render paths call the same parseMap. The difference is what reaches it: Builder pipes its in-memory layer through a serializer that synthesizes visualChannels.colorField from the aggregation expression, then calls parseMap. fetchMap reads the saved JSON straight from the API, where that field can be null, and resolveCustomAggregation bailed out on !field — so no accessor was built and the layer fell back to its fixed color.

The compiled alias identifies the aggregated column on its own (the function already overwrites accessorKey with it), so the field was only a carrier for name/type. Synthesize it from the alias instead of dropping the channel.

Story: sc-561439

Builder

Captura de pantalla 2026-07-30 a las 13 32 56

FetchMap before

Captura de pantalla 2026-07-30 a las 13 12 43

FetchMap after

Captura de pantalla 2026-07-30 a las 13 25 22

What changed

  • src/fetch-map/parse-map.ts — split the !field || !expression guard so a missing field with a live expression synthesizes {name: alias, type: 'integer', accessorKey: alias}. Empty expression still drops the channel.
  • test/fetch-map/parse-map.spec.ts — two tests in the existing custom aggregation block.

resolveCustomAggregation is shared by all five custom-aggregation channels (color, strokeColor, size, height, radius), so this covers them all. It also repopulates scales.*, which was left empty — that's what legends read, so the legend came back with it.

Review focus

The guard change is the whole fix; only one input combination behaves differently:

field expression before after
yes yes alias path unchanged
yes no dropped unchanged
no yes dropped synthesized
no no dropped unchanged

Worth questioning: type: 'integer'. It's inert for rendering here (getColorAccessor only reads name/colorColumn/accessorKey, and calculateDomain keys off the name), but it surfaces in scales.*.field, which consumers read for legends. I matched the value Builder persists so both synthesis sites produce identical descriptors — say if you'd rather it were real, since a SUM(x)/SUM(y) ratio isn't an integer.

How to validate

Against a saved H3 map whose layer has colorAggregation: "custom", a non-empty colorAggregationExp and visualChannels.colorField: null:

const {layers} = await fetchMap({cartoMapId, accessToken, apiBaseUrl});
// before: getFillColor is the layer's fixed color array, scales.fillColor is {}
// after:  getFillColor is a function, scales.fillColor.field.accessorKey is the compiled alias

Evaluating the accessor across the configured colorAggregationDomain yields distinct colors from the palette rather than one repeated value. Verified end-to-end on the reported map (c4625c4a-4a61-4bae-839c-f236dce04a37): flat teal [18,147,154,230] before, the full Global Warming ramp #5A1846 → #FFC300 after. Before/after screenshots are on the story.

Tests

yarn test — 460 pass, 2 skipped, no type errors. Lint and Prettier clean.

The new tests cover the regressing case (accessor built, scale quantize, and the ramp actually varies — a constant accessor would satisfy a weaker assertion) and the empty-expression case, which must keep dropping the channel.

Risk

Downstream consumers were checked against a local build of this branch: workspace-www builder suite (2549 tests), maps suite (194), and mcp-app (113) all pass.

One behavior change worth flagging beyond the bug being fixed: in cloud-native, MapPreviewContainer can load a map from the backend and hand it to parseMap without the Builder serializer. Custom-aggregated layers whose saved config lacks the field go from flat to graduated there too — which is the intent, and it converges with what the Builder canvas already shows, but it is a visible change on that surface.

AI-generated code

Yes, written with Claude Code. The guard change and the type: 'integer' choice are the parts worth a human eye.

…s null

A channel styled by a custom aggregation carries its expression in visConfig and
can reach parseMap with no visualChannels field: Builder synthesizes one before
rendering, but a map persisted by another writer does not. resolveCustomAggregation
bailed out in that case, so no accessor was built and the layer fell back to its
fixed color — a graduated H3 map rendered as a flat fill through fetchMap while
Builder showed it correctly.

The compiled alias identifies the aggregated column on its own, so synthesize the
field from it instead of dropping the channel. Covers all five custom-aggregation
channels and restores the scale that legends read.

Story: sc-561439

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@padawannn padawannn added the bug Something isn't working label Jul 30, 2026
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@padawannn
padawannn marked this pull request as ready for review July 30, 2026 11:34
@padawannn
padawannn requested a review from a team as a code owner July 30, 2026 11:34
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@padawannn
padawannn requested review from Zulcom and zbigg July 30, 2026 11:35
@padawannn
padawannn merged commit 2a5ba9e into main Jul 30, 2026
3 checks passed
@padawannn
padawannn deleted the fix/sc-561439/custom-aggregation-null-color-field branch July 30, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants