Align with agent skills - #128
Draft
nslavin wants to merge 13 commits into
Draft
Conversation
style_builder_tool defaults to Standard and exposes lightPreset, so it could already generate the one combination that is broken by construction: a custom fill/line/circle layer with no emissive strength, on a night-lit basemap. The word "emissive" did not appear anywhere in the repo. Two changes to what the tool emits on Standard styles: - Every custom layer gets a slot. Omitting slot is not a neutral default on Standard — the layer lands above every basemap layer including street labels. When a caller leaves it off, infer one from the geometry type (fill/raster → bottom, line/circle/heatmap → middle, symbol → top) and report the choice in corrections rather than applying it silently. fill-extrusion keeps no slot, since 3D geometry participates in the scene's depth. - fill/line/circle layers get emissive strength 1. These three default to 0, which lets the scene light the layer into shadow under dusk and night. Symbol layers are excluded because icon- and text-emissive-strength already default to 1, and fill-extrusion is excluded because it should be lit by the scene. Also corrects the guidance the tool gives about itself. The description called slot "OPTIONAL" and described omitting it as a default; the schema described 'top' as "above all base map features (default)" while the description of the same field correctly said "behind Place and Transit labels". Both now match the style spec, and neither claims a default that the optional field does not have. global_settings.mode:'dark' predates light presets and only recolors the layers this tool emits, so on Standard it produced a half-dark map fighting the basemap. It is now Classic-only, with a correction pointing at standard_config.lightPreset:'night'. Same for background_color, which Standard supplies through the import. label_color was never read by the implementation at all, and now says so instead of looking functional. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prompts were the furthest out of alignment in the repo: slot, emissive, lightPreset and standard all appeared zero times across all seven of them, while style_builder_tool in the same server says "ALWAYS use standard". create-and-preview-style defaulted to streets-v12 and, in step 3, told the agent to hand-author a classic style from scratch — a raw streets-v8 source plus a #f0f0f0 background layer — bypassing style_builder_tool entirely. It now defaults to standard, builds through the tool, and starts from an import rather than a layer list. Dark maps keep base_style standard and set lightPreset. design-data-driven-style shipped a red→green RdYlGn ramp as its recommended diverging scheme, roughly 270 lines above its own "Use red/green combinations (colorblind-unfriendly)" warning. Replaced with ColorBrewer RdBu, matching mapbox-data-visualization-patterns, with the reasoning stated where the ramp is rather than only in the summary. The categorical example keeps Set1: red and green in an unordered palette with a legend is fine, and the rule is about ordered ramps, so the distinction is now spelled out instead of implied. Every example layer in that prompt also gained a slot and emissive strength, including circle — the case cartography's original rule omitted, and the one that matters most since circles are usually the foreground data. The heatmap example gains a slot but explicitly no emissive strength, because there is no heatmap-emissive-strength property to set. prepare-style-for-production validated expressions and text contrast but checked neither of the two things that actually break a Standard style, both of which produce perfectly valid JSON. Added a step that flags custom layers missing a slot and fill/line/circle layers missing emissive strength, without flagging symbol layers or fill-extrusion, which are correct as they are. Its contrast check no longer assumes a hand-authored background either — it asks which lightPreset the text sits on, and requires passing both when the app switches presets at runtime. The mapbox-style-layers resource is what style_builder_tool points agents at for layer authoring, and it had no mention of slots, emissive strength or occlusion in 345 lines. It now leads with a Standard section and lists the three emissive-strength properties and line-occlusion-opacity alongside the paint properties they belong with, including their real defaults. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README's create-and-preview-style example asked for "a dark-themed map for nighttime navigation" and passed base_style: "dark-v11" — the exact anti-pattern the prompt now avoids. It keeps standard and notes why the light preset is the better control: it relights the basemap coherently and can change at runtime without reloading the style. docs/STYLE_BUILDER.md had no mention of slots or emissive strength. Added a Standard section covering config-before-layers, the three slots, and the emissive-strength defaults, plus troubleshooting entries for the four symptoms these actually present as — data covering the street labels, a layer that looks right by day and vanishes at night, a route disappearing behind buildings, and dark mode that only half-applies. skills/README.md advertised "10 comprehensive skills" and listed 10; the skills repo ships 19. The nine missing ones were data-visualization, flutter, geospatial-operations, location-grounding, both mcp-*-patterns, both search-*, and store-locator. Regrouped so the categories fit what is actually there, and added a short section naming mapbox-cartography as the source of truth for map-design doctrine, with the specific rules that keep showing up as bugs — so the next person changing one of them knows both repos have to move together. Added the Mapbox design vocabulary this guidance depends on to the cspell dictionary, mirroring the same change in mapbox-agent-skills. This does not fix the pre-existing spellcheck failures elsewhere in the repo, which are mostly test fixtures and style-spec field names; spellcheck is not part of CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One existing test asserted the behavior this change corrects — it required that custom layers on a Standard style carry no slot, on the reasoning that "no slot means the layer appears above all existing layers." That is accurate about what the renderer does and wrong about what anyone wants, so it now asserts a slot is always present and that the inferred choice is reported rather than silent. New coverage for the failures that were previously unrepresented: - Slot inference per geometry type, and that explicit slots still win - Emissive strength on fill/line/circle, absent on symbol, absent on Classic styles where there is no lighting to shadow the layer - global_settings.mode:'dark' on Standard steering to lightPreset - The diverging ramp asserts the RdYlGn endpoints (#d7191c, #1a9641, #a6d96a) are gone and the RdBu ones are present. A test that only checked for the word "Diverging" passed against the red→green ramp for as long as it shipped - No heatmap-emissive-strength, which does not exist as a property - The style-layers resource documents all three emissive properties, occlusion opacity, and that symbol layers need nothing prepare-style-for-production's step assertions move from 5 to 6 now that the Standard placement check sits between contrast and optimization. The tool-list snapshot updates for the rewritten style_builder_tool description. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flagged in review as a probable Classic-style leftover on the Standard branch. It isn't one, and the reason is worth writing down where the line is. A sprite has to match the icon vocabulary of the data source, not the basemap being imported. The symbol layers this tool emits reference icons by maki name — either ["get", "maki"] straight off the Streets v8 maki field, which carries 101 names like "restaurant" and "cafe", or a literal default like "marker-15". The Streets sprite is the sprite built for that data, so it resolves those names. Standard's own icons cannot serve those layers. An import is a separate scope whose contents are not addressable from the importing style's layers, and Standard carries its own iconography rather than exposing maki names. Per the style spec, a sprite is required once any layer uses icon-image or a *-pattern property, and it is the root style's sprite that serves the root style's layers. So the value stays. The test pins the pairing rather than the string, so anyone switching the sprite has to confront the icon names that depend on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most of this PR is text a model reads — tool descriptions, prompts, resources — and unit tests can only assert that the text shipped, not that it changes agent behavior. So this adds an eval for the surface an agent actually consumes. scripts/eval-tools.mjs hands the model the real tool definitions off the registry (descriptions plus zod schemas converted to JSON Schema), gives it a task, and inspects the tool calls it makes. Where the model calls style_builder_tool the tool is executed for real — it needs no network — so the checks run against the style JSON that would actually ship. Checks are deterministic predicates, not an LLM judge, because the output is structured; that keeps the score stable so a move in it means a move in behavior. Five cases: dark mode, custom data layers, config-before-layers, a diverging ramp, and whether style creation routes through the builder. Against origin/main: 83.3%, 88.9%, 83.3% over three runs. With this branch: 100% three times. The checks that consistently failed on main were exactly the rules this PR adds — emissive strength on custom layers, line-occlusion-opacity on routes, and a colorblind-safe diverging scheme. The eval also found what unit tests could not. create_style_tool's schema said a style "must include: version (8), sources, layers" and never mentioned imports, so an agent reading it hand-authors a Classic style — the tool builds nothing, it uploads whatever it is handed. Both its description and that schema now name imports, point at style_builder_tool, and say dark mode is lightPreset:'night'. Worth recording for whoever picks up the next round: style_builder_tool cannot add a user's own data. It only restyles Streets v8 basemap layers, with no sources parameter and no GeoJSON path, so the slot and emissive-strength handling it now does never reaches the commonest case — the user's own zones, routes and points. That style gets hand-authored and uploaded through create_style_tool, where the improved description is the only thing steering it. A builder that accepts a GeoJSON source would close it properly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r own data Four open items, and they turned out to share a root cause: the builder treated Standard and Classic as one code path with nine scattered `isUsingStandard` checks, one of them a positional boolean threaded into layer creation. Every new rule added another branch, and options for the wrong target looked supported while doing almost nothing. The divergence now lives in one place. A StyleTarget descriptor says what each target is: whether slots apply, whether the scene is lit, whether a background layer is needed, and which options belong to the other target. Shared machinery — Streets v8 resolution, filters, paint and expression generation — stays shared. Wrong-target options are now rejected with a pointer to the option that works, rather than silently dropped. `global_settings.label_color` on Standard names colorPlaceLabels; `standard_config` on Classic says Classic has no config surface. Enforced at the tool boundary rather than in the schema because tool registration reads `inputSchema.shape`, which only exists on a plain object schema — a discriminated union would break registration in every MCP client, and its `oneOf` output is the kind of complex schema this repo has already been bitten by. custom_sources closes the gap the eval exposed. The builder could only restyle Streets v8 basemap layers, so a user's zones, route or store points had to be hand-authored and uploaded raw, which is where slots and emissive strength got lost. Declare a GeoJSON or vector source and point a layer at it with source_id. Placement deliberately differs from basemap layers: a basemap fill belongs in `bottom` under the roads, but a fill of your own data is an overlay and belongs in `middle`. Lines get line-occlusion-opacity, since the canonical user line is a route and basemap roads are correctly hidden by buildings. label_color now works. It was declared but never read, and reinstating it naively still did nothing: the generic per-property defaults had already put a literal text-color in place and the new code deferred to it. Precedence is now explicit — a colour on the layer, then label_color, then the dark-mode default, all beating the generic default. Icons come from each layer's own icon field. The maki branch was keyed on 'poi_labels' and 'transit', neither of which is a real source layer, so it was unreachable and every symbol layer picked up a hardcoded "marker-15" — one generic pin for every feature, and a pin on every city label. Now poi_label and airport_label resolve ['get','maki'], transit_stop_label prefers maki over network because network is null for most stops, and place_label gets no icon. Eval: 19/19 across three runs, including a new check that the model actually finds and uses custom_sources rather than falling back to hand-authoring. Baseline on origin/main was 83.3–88.9%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four things the builder reported doing and didn't: `action: "hide"` hid a feature by omitting its layer, which works on Classic — the tool authors the whole stack there — but not on Standard, where the basemap keeps drawing it through the import. The summary said "Hidden" either way. On Standard the tool now sets the matching config toggle (poi_label → showPointOfInterestLabels, and so on) and names it. Water, landuse and the road network have no toggle, so `hide` on those is rejected with a pointer to `theme` and the `color*` overrides. Only `base_style === 'standard'` was ever branched on, so all eight Classic values produced byte-identical output: `dark-v11` was a light `#f8f4f0` map and `satellite-v9` had no imagery. The base name now decides light vs dark and, for the satellite bases, adds a `mapbox.satellite` raster in place of the background. Bases within a group stay equivalent rather than being given invented differences — a Classic base is not an import and cannot reproduce the named style. `slot` sits on the layer, not the top level, so it was invisible to the wrong-target check and was the last cross-target option still dropped in silence. It is now rejected on Classic. Layers from `custom_sources` now report their inferred slot, as basemap layers already did, and a fill is told about `slot: "bottom"` — the overlay default is right for a zone and wrong for a choropleth. Recolouring a basemap feature on Standard still generates the layer, since an overdraw is right for a filtered subset, but names the config property that retints the basemap itself. Prompts, docs and the eval follow: `create-and-preview-style` gave Classic the Standard import block and `lightPreset`, `build-custom-map` and `setup-mapbox-project` read theme words as Classic style names, `debug-mapbox-integration` gains a phase for the Standard failures that throw nothing, `design-data-driven-style` routes data through `custom_sources`, and the eval covers the Classic path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… ids
Review of the previous commit turned up four things the new Standard
`hide` path got wrong, all from one cause: it ran before layer
resolution and keyed the toggle map off the raw `layer_type`.
`layer_type: "pois"` with `filter_properties: { maki: "restaurant" }`
resolves to `poi_label` for every other action, but `hide` rejected it
as unhideable — in a message that went on to list `poi_label` as
hideable. The pass now sits after resolution and keys off the resolved
source layer.
A layer of your own from `custom_sources` was told to use `theme:
"faded"` and the `color*` overrides. Your data isn't in the import, so
`hide` there is omission on either target; the toggles are advice about
the basemap and don't apply.
An unrecognised `layer_type` got a verdict about what Standard can hide
instead of the suggestion list, which diagnoses the wrong problem. It
now returns exactly what the same input returns under any other action.
A `hide` contradicting an explicit `show*` value resolved it in silence.
The hide still wins; it now says so.
Separately, `custom_sources` is merged last so neither target branch can
clobber it, which meant an entry keyed `composite` — or `satellite` on a
satellite base — replaced the basemap source rather than joining it, and
every basemap layer then read the caller's data. Those ids are now
rejected up front with a suggested rename.
Also: the summary takes the build result instead of re-deriving the
Classic settings and the hide toggles from the input, the five
guidance-only early returns in `buildStyle` collapse into one helper,
and the `hide-basemap-feature` eval case loses a check that could not
fail — `layers` is required by the schema, so `Array.isArray` on it was
always true.
Tests cover each of the five behaviours, plus the two gaps the review
noted: `satellite-streets-v12` (the other imagery base) and
`label_color` against a dark mode the base name implied.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A stricter pass over the previous commit, which had propagated one wrong toggle and left a comment claiming more than the code did. `hide` on a building set `show3dObjects`, which is the whole 3D group — buildings, trees, landmarks and facades. So hiding buildings also stripped the 3D trees and landmarks nobody asked about, while the correction line reported only the buildings. It now sets `show3dBuildings`, and that property is exposed on `standard_config` alongside `show3dObjects` for callers who do want all 3D gone. `standard_config.showRoadsAndTransit` was accepted and did nothing. Per Mapbox's docs it is a Standard *Satellite* property; the plain Standard style this tool imports has no such config, and `base_style` offers no Satellite value, so it landed an inert key in the import. The wrong-target check only compared Standard against Classic, never a property belonging to a different Standard. It stays in the schema on purpose — the schema strips unknown keys, so deleting it would trade one silence for another. The `generateSummary` docstring added last commit claimed everything came from the build result, while the layer description six lines down still re-derived from the raw input — so a layer passed as "pois" was described by a name the built style never used. The resolved source layer is now threaded through with the toggles, and the docstring says what is and isn't read from the input. Contradiction reporting is completed rather than half-done: two layers hiding one feature emit one correction instead of two, and hiding a feature while also drawing it is now named — as a filtered-subset technique when the drawn layer has a filter, and as a plain mistake when it doesn't. Both are keyed by the hidden feature, not by the layer that hid it; keying by layer was a duplicate bug this commit's own probe caught before it shipped. Also fixes a test name I had mangled dodging an apostrophe, and covers the gaps: the reserved-source-id check firing on a source no layer references, duplicate hides, and hide-plus-draw both ways. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e surface Review pass over the `custom_sources` path this branch added. Every defect below is the same one the branch exists to remove: an option accepted by the schema and then silently dropped. `createUserDataLayer` wrote the literal `color` only, so `expression` and `property_based`/`property_values` were ignored. A choropleth over your own data therefore came out as a fill with **no `fill-color` at all** — which the spec draws as opaque black over the whole map, with nothing in the corrections to say so. The same `design-data-driven-style` prompt this branch rewrote routes choropleths through exactly this path. Colour now goes through the shared expression builder, and a category `match` always gets a fallback arm: without one an unlisted value draws nothing, so `color` sets it and the colour used is reported when it wasn't given. Fixing that surfaced a second bug in `generateExpression`: it returned `config.expression` whatever property it was asked for, so a layer with both `expression` and `opacity` got the same colour ramp in `fill-opacity`, where the spec expects a number. The expression describes the colour and is applied only there; the zoom-ramp maths moved to `zoomRamp` so the new path can ask for a ramp without inheriting the colour escape hatches. Layer ids collided. They derive from what a layer draws, so a filtered pair off one GeoJSON source both came out `zones-fill`, as did the same basemap feature asked for twice. Duplicate ids are invalid per the spec and the collision was silent; they are suffixed rather than rejected, since asking for a feature twice is legitimate. `render_type: "symbol"` over your own data got no `layout` at all, and a symbol layer with neither `text-field` nor `icon-image` renders nothing — present, valid, invisible. It is labelled from `name` now, with the assumption reported because the tool cannot read the source to check it. Still no `icon-image`: a literal resolves against the Streets sprite and would pin every feature identically, the defect the basemap path just lost. A `fill-extrusion` layer reported `inferred slot "undefined"` and then warned about drawing over the street labels — the opposite of what the code chose, which is to leave real 3D geometry unslotted on purpose. `standard_config` exposed 26 of the 44 documented Standard properties. Because the schema strips unknown keys, the other 18 could be set and dropped with no error. Added the indoor group, the POI land-use colours, the granular 3D toggles, `colorLand`, `colorBuildings` and `fuelingStationModePointOfInterestLabels`; a test pins the list against the reference docs so the next upstream addition fails a test instead of going quiet. `colorLand` also fixes the `background_color` rejection, which pointed at `colorWater`/`colorGreenspace` — neither of which is the land colour, and naming the wrong replacement undercuts the point of rejecting the option at all. The build summary walked a written-out list covering 8 of 15 `show*` toggles and 6 of 22 `color*` overrides, so most of the config surface was applied to the import and then left out of the report, and anything added to the schema was omitted by default. It is derived from what was actually set now. Same class of defect as the `hide` bug: the summary claiming less than the build did. Finally, the surface itself. This branch had taken the per-request cost of the two changed tools from 11,564 to 38,687 characters of description plus JSON Schema — paid on every request to this server, styling task or not, and `style_builder_tool` alone had become 54% of it. The reference-shaped material (the slot table, the emissive-strength explanation, the Streets v8 examples) already exists verbatim in `resource://mapbox-style-layers`, which is fetched on demand, and the per-field detail in the input schema the model is given anyway. The descriptions keep only the rules that change the shape of a call, and the duplicated essays come off `slot`, `action`, `base_style`, `custom_sources` and `render_type`. `style_builder_tool`'s description is now 2,722 characters, 26 below where this branch found it, and the whole 23-tool surface is 32,386 against 28,823 on main — the remaining growth being the 19 config properties above. Not verified: `npm run eval:tools`, which needs a live API key. Every rule its seven cases assert is preserved, but only a real run shows whether a model acts on 2,722 characters as it did on 7,577. Also: `setup-mapbox-project` suggested a satellite recipe (`base_style: "standard"` plus an imagery layer) that this tool cannot build, since `custom_sources` takes GeoJSON and vector tilesets only; the eval destructured a `TOOLS_WITH_UI` export that does not exist and masked the `undefined` with `|| []`, so a tool moved out of `CORE_TOOLS` would have vanished from the measured surface; and `npm run lint` now covers `scripts/*.mjs`, which was the one source file going through no linter or formatter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Classic base authors nothing, so `layers: []` produced a lone background layer reported as a finished style. It is also where a caller lands having read `base_style: "dark-v11"` as "give me dark-v11" — the likeliest reading, and the one thing this tool cannot do, since it builds new self-contained styles rather than fetching existing ones. Rejected now, naming all three readings: reference the real style, configure Standard instead, or list what a self-contained stack should draw. Standard stays exempt, where a config-only style with no layers of its own is the normal shape. A merely thin Classic stack is still built, with the shortfall named. The custom-source path was also still dropping things the rest of the tool rejects: a layer with no `color` came out opaque black, bare hex reached the style unparseable, a `fill-extrusion` had no height and so drew nothing, and `zoom_based` was accepted with nothing to ramp. A user `fill-extrusion` also took the overlay slot, contradicting the reasoning applied to a basemap one 40 lines away — 3D geometry depth-sorts against the scene, and any slot flattens it. Colour normalisation is now one shared helper, which also stops a named colour becoming `#red`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three silent-drop paths on the custom-source layer, which is the least
covered of the new code, plus the eval-harness robustness gaps.
An object `filter` was assigned straight to `layer.filter`. The basemap
path resolves that shorthand against Streets v8 field metadata; custom
data has none, so the object went through verbatim and the spec rejects
it — `validate_style_tool` says "array expected, object found", but only
once `create_style_tool` has the JSON. Converted to an expression and
reported, since the object form is what a caller carries over from a
basemap layer in the same call. A bare string or number is rejected:
there is no property to test against.
A heatmap dropped `color`, `expression`, `property_based` and `opacity`.
`heatmap-color` is a ramp over ["heatmap-density"] rather than a colour,
so it is absent from the colour table, and `heatmap-opacity` was missing
from the opacity table too — both lookups returned nothing and the whole
block was skipped, leaving a layer with no paint reported as built
successfully. This is the render type `design-data-driven-style` names
for point data. `width` on a non-line layer is now named rather than
dropped, without inferring `circle-radius` from a field called width.
The eval hardcoded `thinking: {type: "disabled"}`, which Claude Fable 5
rejects outright and which reduces how readily any model reaches for
tools — the only thing the eval scores. Effort moves to EVAL_EFFORT.
Cases run under allSettled so one rate limit no longer discards every
other result, MAX_TURNS exhaustion is marked TRUNCATED instead of scored
as a complete transcript, and a schema that fails to convert warns.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Align
style_builder_toolwith the Standard-first workflowSeparates the Mapbox Standard and Classic code paths in
style_builder_tool, andfixes the options that appeared supported on the wrong target while doing nothing.
Both bases previously shared one code path gated on nine scattered
isUsingStandardchecks. Options belonging to the other target were accepted and dropped, so a caller
could set
global_settings.mode: "dark"on Standard, orsloton Classic, and get asuccess report describing a style that ignored them. A single
StyleTargetdescriptornow owns the difference, and wrong-target options are rejected with a pointer to the
one that works rather than silently discarded.
Principal fixes
base_style === 'standard'was everbranched on, so all eight Classic values produced byte-identical output:
dark-v11was a light map and
satellite-v9had no imagery. The base name now determineslight/dark and whether
mapbox.satelliteraster tiles sit underneath.action: "hide"did nothing on Standard. Hiding worked by omitting the layer,which the import redraws. It now sets the matching
standard_configtoggle andreports which; features with no toggle (water, landuse, roads) are rejected with an
alternative rather than reported as hidden.
custom_sourcesaccepts GeoJSON and vectortilesets, with slot, emissive strength and
line-occlusion-opacityapplied — theproperties that were lost when layers had to be hand-authored and uploaded raw.
standard_configcovered 26 of 44 documented properties. The schema stripsunknown keys, so the remaining 18 could be set and dropped without error. A test
pins the list against the reference documentation.
layersarray reported success for a lone background layer.It is now a redirect naming the three things the caller likely meant.
Full per-fix detail, including the data-driven styling and layer-id collision fixes,
is in
CHANGELOG.md.Also included
resource://mapbox-style-layersupdated to Standard-first guidance;create-and-preview-stylepreviously emitted Standard instructions for Classic bases.npm run eval:toolsharness scoring tool selection and generated style JSONagainst deterministic predicates, using the real tool definitions off the registry.
skills/is now a pointer tomapbox/mapbox-agent-skills.npm run lintextended toscripts/*.mjs, previously covered by no linter.Verification
tsc --noEmitandeslintclean.@mapbox/mapbox-gl-style-specin tests.Breaking changes
None to tool names or output schemas. Inputs that previously succeeded while being
ignored are now rejected with guidance:
global_settingson Standard,standard_configandsloton Classic,standard_config.showRoadsAndTransit,action: "hide"on an untoggleable Standard feature, an empty Classiclayersarray, and a
custom_sourceskey colliding withcompositeorsatellite.