Changelog
Unreleased#
No version has been released, and the packages are not yet published on npm. All seven packages
carry the placeholder version 0.0.0.
Until the first release, the source of truth is this handbook together with the API reference, both describing the code as it is in this repository. Breaking changes can happen at any time before the first release.
Recent changes affecting the public API#
-
The packages are built for release, and the license key exists. Each package now builds to
dist/(ES modules, type declarations, source maps;npm run build), and itsexportscarry the conditionssource,typesandimport. The packages still carry"private": trueand the version0.0.0; nothing is published.@vectorstream/prochecks a signed license key offline and shows a watermark without a valid one; no function depends on the key. See Free and Pro and Extensions; the key is described in Setting up the license key. -
Free and Pro are separate packages. Everything the free package (
@vectorstream/editor, MIT) does not do on its own now lives in@vectorstream/pro: the ten rule kinds beyond'innerhalb'and'ueberlappungsfrei', attribute conditions, rules in meters, the red preview, snapping onto target layers, the incremental check of rules, the circle as a cutting shape (CutCircleMode,DifferenceCircleMode) and the list of violations (@vectorstream/pro/ui). The editor takes extensions throughEditorOptions.erweiterungenanderweiterungGlobalAnmelden;OHNE_GLOBALEbuilds an editor that ignores the announced ones;editor.regelarten()lists the kinds of an editor. Removed from the free package:ARTEN,REGELARTEN,EingebauteArtandbedingungErfuellt(now in@vectorstream/pro),CutCircleModeandDifferenceCircleMode,Vorschaupruefungand the two circle entries ofdefaultToolbarEntries()(21 entries;@vectorstream/probringsmitProWerkzeugen). The pure check functions (pruefeRegel,regelRoh…) know the two free kinds when no registry is handed in. Documents with rules of the other kinds lose nothing in the free editor (see Compatibility). -
Rules of a kind the editor does not know are kept. A stored rule is read in two stages (envelope, then kind). One whose kind is not registered, or that has a condition no extension can evaluate, is listed by
editor.regeln()withstatus: 'benoetigtErweiterung'and its stored text unchanged; it is never checked, can be removed but not changed (ruleNeedsExtension), and the import of the application no longer drops it. New exports:huelleRoh,ersetzeZiel,PRO_ARTEN, the typesFremdregel,Regeleintrag,Regelhuelle.editor.regeln()now returnsRegeleintrag[]. See Compatibility. -
Minimum width finds long convex slivers. When the cell search of
'mindestbreite'runs out of its 20 000 cells, the convex hull of the outer ring decides: a hull narrower than the circle is reported. Rules that used to let a long thin strip or triangle pass now report it; nothing new is reported that the circle would fit into. See Minimum width. -
Violations box above the layer tree in the application: always in view, the tree scrolls in itself. See The list of violations.
-
Attribute keys named like
Object.prototypemembers. A GeoJSON property called__proto__used to be dropped silently on import; it andconstructor,prototype,toStringand the like are now ordinary attributes (import,SETATTR, undo/redo, export, saved documents). Attribute sets are objects with anullprototype, soattributeVon(p, id)has no inherited members ('constructor' in setisfalse) andObject.prototypecannot be reached through an attribute. New helpersneuerAttributsatz,setzeSchluessel,hatSchluesselfor code that builds attribute records. See Labels and attributes. Breaking for code that treats a set like a plain object:set.hasOwnProperty(...),String(set)and${set}now throw aTypeError; useObject.hasOwn(set, key)orhatSchluessel(set, key).Object.assign({}, set)loses a__proto__key (it uses the setter); spread,Object.entries/Object.fromEntries,structuredCloneand JSON keep it. -
Minimum distance near the poles.
'mindestabstand'no longer misses a pair at a large longitude difference close to a pole (see Meters). -
importGeoJSONchecks the geometry. New optionImportOptions.geometriePruefen(defaulttrue): the result is run throughcheckGeometry, and findings (overlapping areas, crossing edges, a node on a foreign edge) throw anImportErrorwith code'core.importInvalidGeometry'and the findings inviolations. PassgeometriePruefen: falsefor trusted documents or to open legacy data. Behaviour change: data that used to import (and later broke an edit) is now rejected. See GeoJSON. -
Red preview. With an active rule on the active layer, the preview of the drawing and editing tools turns red when the next action would be refused; violating legacy is drawn as
invalid. See The red preview. -
Showing an element on the map.
Adapter.bereichZeigen(bereich, randPx?)(all three map adapters andNullAdapter.gezeigteBereiche), the typeBereich, andeditor.zeigeElement(layer, element). A customAdaptermust implement the new method. See Rules between layers. -
More kinds of rule between layers.
'beruehrt','beruehrungsfrei','schneidend','abdeckung','lueckenlos','aufLinie','aufRand','mindestabstand','mindestflaeche','mindestbreite'; a measuremassand an attribute conditionbedingungper rule;Regel.zielcan benull;Regelverstoss.elementEbene;ARTEN,Bedingung,bedingungErfuellt,Laengenmass,laengenmassAus. Older builds read the new kinds as unreadable. See Rules between layers. -
Package names. The packages are named
@vectorstream/core,@vectorstream/adapter,@vectorstream/editor,@vectorstream/toolbar-ui,@vectorstream/leaflet,@vectorstream/openlayersand@vectorstream/maplibre. -
@vectorstream/toolbar-uiis new: ready-made toolbar buttons built onToolbar— groups, separators, the tool icons, and on/off toggles for the snapping switches — for an application that would rather not draw its own (see Toolbar). Optional and separate from@vectorstream/editor;Toolbaritself still draws nothing. -
Language. User-visible texts come from message tables; English is built in, German is opt-in through
registerLocale(see Language). Rejections carry a namespacedcodeandparams; the formerMELDUNG_*text exports were removed. -
Transaction labels are stable English identifiers in kebab case (
'draw-polygon','merge-faces', …), see Events. -
Lines are inserted exactly.
insertLineaccepts only tolerance0(or none) and throws aRangeErrorotherwise; inserting the same line again applies no operation. -
Rotation pivot.
TransformMode.setzeDrehpunkt(point | null)fixes the center the next rotation drag turns around, in place of the selection's bounding-box center. Programmatic only — no new handle; the pivot is consumed by the next rotation grab, and the one after that falls back to the center again (see Modes, sectiontransform). -
Utilities page. A new Utilities page collects the pure, standalone helper functions each package already exports. New export:
kreisPolygon(@vectorstream/core) — the circle/ellipse n-gonDrawCircleModedraws with, now a function of its own instead of living only inline in that mode. -
Circle as a cutting shape. Two new modes,
CutCircleMode(keyH) andDifferenceCircleMode(keyJ), the circle variants ofcut/difference(see Modes): drag a circle instead of placing a ring, same carve otherwise. New transaction labels'cut-circle'and'difference-circle'. The semi-axis computation, drag state, and measurement displayDrawCircleModealready had are now shared with the two new modes instead of duplicated, via an internal module — not a public export of@vectorstream/editor. -
Image export. New
Adapter.exportImage(): Promise<Blob>, the same call on every renderer — a PNG of the current view at screen resolution, background tiles included, with the same CSS tile darkening the screen shows and the map's attribution line drawn into the bottom-right corner (see Exporting the map view as an image). Breaking for a custom implementation ofAdapter(review finding M7):exportImageis a required method, same as every other one on the interface — before the first release, adding one without a minor/major bump is accepted the same way earlierAdapteradditions were. Leaflet redraws faces/edges/points/labels from the currentScene; MapLibre does the same for points and labels only (faces/edges stay native); OpenLayers composites its own layer canvases (new exports of@vectorstream/adapter:zeichneFlaechenAufCanvas,zeichneKantenAufCanvas,ladePunktBilder/zeichnePunkteAufCanvas— now split into an async preload step and a synchronous draw step, review findings N3/N4 —zeichneBeschriftungenAufCanvas,zeichneAttributionAufCanvas,pngBlobVonCanvas,alsPngExport). Handles, snap markers and guide lines never appear in the image on any of the three: on OpenLayers/MapLibre, the layer they'd otherwise share with free-standing points is hidden, redrawn, read and restored in one synchronous sequence (noawaitanywhere in it) and only then is the canvas handed back — nothing can observe the hidden frame, and two overlappingexportImage()calls on the same adapter can no longer leave that layer hidden for good. A tainted background (no CORS permission on the tile server) rejects with the newExportError—adapter.exportCorsBlockedattoBlob/toDataURL/getImageData, where tainting actually throws (not atdrawImage, which only marks the canvas, silently);adapter.exportFailedfor every other encode failure. The app's own Leaflet map now setscrossOrigin: trueon its tile layer accordingly. -
Customizing the look. New
darstellungoption onLeafletAdapterOptions,OpenLayersAdapterOptionsandMapLibreAdapterOptions(see Customizing the look): a partial, per-field override of the editor's colors and line styles, merged with the built-in defaults once, at construction. New exports of@vectorstream/adapter:baueDarstellung,mitUeberschreibung,Darstellung,DarstellungUeberschreibung,TeilUeberschreibung,STANDARD_DARSTELLUNG,DarstellungFehler(codeadapter.darstellungUnbekannterSchluesselfor an unknown state/field, thrown at construction) — plusBESCHRIFTUNG_FARBE/BESCHRIFTUNG_GROESSE, now exported for the same reason every other style table already was.griffDarstellung,punktDarstellung,hilfslinienStil,beschriftungsStil,schildRahmenAus,zeichneFlaechenAufCanvas,zeichneKantenAufCanvasandzeichnePunkteAufCanvas/zeichneBeschriftungenAufCanvasall take the resolved table set as a new, optional trailing parameter (default: the built-in appearance) — existing callers that never pass one see no change.exportImagedraws from the same override the map itself uses. Not overridable, by design: the icon-based symbols (snap markers, the rotate/scale handles' own icon, the pin shape's color) — already distinguished by shape rather than color alone. New type export:Weiten(widens the twoas constsingle-object tables' literal types for the override). Fix round 1 (review findings I1/I2/M1/M2/M3/M5/M7/M9): a dash pattern (strich) is now overridable on every state the style type allows, not only states whose default already has one; an explicit override offlaechen.normal/kanten.normalnow always wins over a per-layer color (ebenenFarbe) for the fields it names, documented and tested; an explicitlyundefinedfield no longer blanks out the default; validation rejects inherited/prototype keys and non-object values instead of silently accepting or ignoring them;Darstellung's keyed fields are nowtypeof <their default table>instead of a second, hand-written key union;mitUeberschreibungcopies an override's array fields instead of keeping them by reference, and every default table (plusSTANDARD_DARSTELLUNG) is deep-frozen; the exported image's attribution line now follows an overridden font family; an enlarged circle handle radius now enlarges its own hit area too (never shrinks it below the ordinary tolerance). Fix round 2 (re-review finding N1, the real bug, plus M3/M5/M10):strich: []— the documented way to force a solid line on a state whose default is dashed — made the line disappear entirely on MapLibre (linienWerte'scoalescekept the feature's own empty array instead of falling back to the solid pattern); fixed, with a pixel test, and confirmed (new tests) that Leaflet, OpenLayers and the canvas export were never affected.ladePunktBilderlost itsdarstellungparameter — it never actually consulted it (pin selection depends only onScenePoint.form, and the pin shape isn't themable) — removed rather than kept "for symmetry" with no effect. Handbook: thehilfslinien.invalid"becomes overridable automatically" claim is corrected —SceneGuidehas nostatefield, so that one needs a design decision and code change, unlikekanten.invalid/punkte.invalid; and a new note that override values reachcssTextunchecked (familie,hintergrund), so an application should never pass through untrusted input there. -
Large data sets (see Large data sets). Measured first: 50,000 points plus a 150 × 150 parcel grid cost 52–64 ms per scene without handles, three to four 60-Hz frames. Two measures, both automatic: the editor builds the scene only from what lies in the visible map area (plus half a view of margin, selected elements always included), and below zoom 15 it leaves node and midpoint handles out (selected ones excepted) — free-standing points are unaffected, see the fix-round note below. With a 20 × 20-parcel window the scene costs 1.2 ms. New, all additive: optional
Adapter.viewport?()and theViewporttype, implemented by all three adapters and settable onNullAdapter(viewportoption,setViewport());sichtfeldAusEcken(@vectorstream/adapter);SpatialIndex.facesInBox()andSpatialIndex.extent()(@vectorstream/core);SceneOptions.viewport/index,EbenenSzeneOptions.viewport/indexFuerand the constantGRIFFE_AB_ZOOM(@vectorstream/editor, renamed fromPUNKTE_AB_ZOOMin fix round 1, see below). Behavior change: on a Web Mercator map below zoom 15 only selected nodes can be grabbed, because their handles are the only ones in the scene. Fix round 1 (review findings I1–I5):MapLibreAdapter.viewport()now reportsmap.getBounds()instead of projecting the four container corners by hand — past about 72° pitch (reachable once an application raisesmaxPitchbeyond the default 60) a corner above the horizon projected to a point behind the camera, and the box built from it excluded most of what was actually on screen;OpenLayersAdapter.viewport()now derives its zoom from the view's resolution (Math.log2(156543.03392804097 / view.getResolution())) instead of trustingView.getZoom(), which is only the Web Mercator tile level for OpenLayers' own default resolution ladder and becomes an index into a customresolutionsarray (the WMTS-typical case) otherwise. BelowGRIFFE_AB_ZOOM,EditModenow suppresses the edge grab a click near an unselected, hidden corner's position used to fall through to — such a click does nothing now, matching what the handbook always claimed, instead of silently moving the whole straight line through that corner. Scope correction: free-standing points are no longer part of this threshold at all — the renamed constant now hides handles only; points were hidden in the first version of this feature despite the feature's own benchmark showing they are cheap (user ruling of 13.10.2026, see Large data sets). -
Three more languages: French, Spanish, Dutch. Short draft gap 7, "Weitere Sprachen", user decision of 12.10.2026: complete tables for
@vectorstream/core,@vectorstream/editor,@vectorstream/adapterand the app, the same four packages German already covers — new exportscoreFr/editorFr/adapterFr,coreEs/editorEs/adapterEs,coreNl/editorNl/adapterNl(app's ownappFr/appEs/appNlare not published, same asappDe), registered the same way (registerLocale(tag, messages, table)) and checked the same way (defineTranslation) as German — see Language. Machine-translated, not reviewed by a native speaker (the user's own explicit, informed risk acceptance, not an oversight): every key, placeholder and plural form is still checked by the compiler and by tests the same way German's is, but wording may read oddly until someone who speaks French, Spanish or Dutch looks it over. French's own plural rule is the one surprise worth knowing before extending any of the three further:Intl.PluralRules('fr')selects"one"for bothn = 0andn = 1, not onlyn = 1as German, English, Spanish and Dutch all do; French (and, separately, Spanish) also has a"many"category for round millions that every French/Spanish plural entry here leaves out on purpose, sincerender()already falls back to"other", the correct text for that case too. The handbook's "your own translation" example no longer uses French for its hypothetical custom, partial table (confusing now that French is a real, complete, built-in one) — it uses Italian instead. -
Switchable snap-target kinds. New
editor.rastArten: readonly Rastart[](default all four, in rank order: corner, edge midpoint, quarter point, edge), the global setting every tool falls back to when it leaves the choice open on its ownctx.rasten(...)call (see Snapping). An empty array switches snapping off kind by kind, the same effectrastenAktiv = falsehas all at once. Short draft gap 5, "Einrast-Ziele einzeln schaltbar", user decision of 12.10.2026: one global setting for every tool, all four kinds individually switchable — not three as in Geoman, which has no quarter point.