# OnlyMapJS > Agent-first map library: a declarative HTML manifest drives deck.gl. You write `` / `` / `` / `` / `` elements; the library owns reconciliation, accessor compilation, basemap/view sync, overlays, and validation. No build step, no imperative deck.gl code. OnlyMapJS is NOT raw deck.gl and NOT generic HTML/JSX. The rules below are the delta from what you already assume — following them produces correct manifests on the first pass. Validate with `OmMap.validate(htmlString)` (structured errors AND warnings, each with a `fix` instruction — heed both; an "unknown attribute" warning means a prop is silently dropped) and inspect resolved output with `OmMap.snapshotIR(htmlString)` before finalizing. Programmatic/native bridge rule: `MapController.setLayers()` accepts normal function accessors and, only for schema-declared accessor props, restricted expression strings (`props: {getPosition: "[$lon, $lat]"}`) so a descriptor can cross JSON safely. Verify that lane with `OmMap.snapshotDescriptorIR(descriptors)`; URL data is represented as pending and is not fetched. Active descriptors own reference-counted data transports: removal/identity change/destroy releases them; `MapController.suspend()` releases live work and `resume()` reacquires it, repainting the last rows so a foregrounded map is never blank while the first response is in flight. Live sockets and poll loops no longer outlive their layer — code that relied on that must keep the layer mounted. For app-scoped packaged licenses, `configureLicense(key, {appId})` takes an exact platform identifier obtained by the native host — never copy an app id from page/bridge input, and prefer keys scoped by both `domains` and `apps`, since only the domain claim is pinned by the browser. ## Loading the library - npm projects: `import "@nika-js/onlymap"` + `import "@nika-js/onlymap/onlymapjs.css"`. - No-build/CDN pages (any HTML file that travels as an artifact): load the single-file standalone bundle from a raw-file CDN — `` (resolves to `dist/onlymap.standalone.js`; pin `@` in files meant to be shared) — plus ``. The stylesheet is `dist/onlymapjs.css`, NOT `dist/style.css` (an alias ships for that guess, but write the canonical path). - NEVER load from a rebundling CDN (esm.sh, skypack, jspm): re-bundling the code-split dist duplicates the deck.gl/luma.gl runtime across lazy chunks, and every layer then fails shader compilation (`DECKGL_FILTER_*: no matching overloaded function`) while the basemap still renders. Raw-file CDNs (unpkg, jsDelivr) are fine, for the code-split `dist/onlymapjs.js` too. ## Rules that differ from your training priors - Always write explicit closing tags: ``. NEVER self-close (``) — HTML5 ignores the slash on custom elements and every following sibling silently becomes a child. - Attributes are kebab-case, not camelCase: `get-fill-color`, `radius-units`, `line-width-min-pixels`. Every hyphenated attribute maps to the camelCase deck.gl prop. - Accessors are expressions in attributes, not JS functions: `get-position="[$lon, $lat]"`, `get-radius="$population * 0.001"`. `$field` reads a datum field; the library resolves flat vs. GeoJSON shape for you — never write `d.properties.x`. - ScatterplotLayer points need an explicit size: `radius="6" radius-units="pixels"` (constant), `get-radius="..."` (data-driven), or `radius-min-pixels="..."` (floor). Deck's default is getRadius 1 METER — sub-pixel at city zooms, the layer renders nothing visible — and validation warns when no radius source is present. - A literal color inside a `get-*` accessor is a string IN the expression — quote it: `get-line-color="'#ffffff'"` or an RGBA array `get-line-color="[255,255,255,200]"`. Bare `get-line-color="#ffffff"` is an expression parse error (plain attributes like `color="#dc2626"` take bare hex, accessors do not). - Inline event handlers (`onclick="..."`) are rejected. Use `data-emit` attributes (``) or `addEventListener` inside a `