Marker model and category placement
getbased separates three concerns that used to look like one identifier:
This separation lets a user organize markers without rewriting health data. It also lets contributors split and evolve the schema without forcing existing profiles to migrate or re-import results.
Schema authoring and generation
The marker catalog is authored as category modules underjs/marker-schema/. js/marker-schema/index.js composes those modules in stable category order. scripts/build-marker-schema.mjs generates js/marker-schema.js, and js/schema.js remains the stable compatibility facade used by the rest of the application.
js/marker-schema.js directly. Make changes in the authoring modules, preserve the intentional category order in index.js, then regenerate the artifact.
Built-in marker identity
Every built-in marker has an immutablegb:marker:* ID authored in js/marker-schema/identities.js. The generated catalog provides both forward and reverse resolution:
getBuiltinMarkerId(dotKey)resolves a current or legacy storage location to its stable ID.getBuiltinMarkerDotKey(markerId)returns the current storage location.resolveBuiltinMarkerDotKey(dotKey)resolves a current or legacy location to the current one.
- Keep its
gb:marker:*ID unchanged. - Update
currentDotKey. - Add the previous location to
legacyDotKeys. - Add focused round-trip tests for every affected dot-keyed surface.
Custom marker identity
Custom marker definitions remain keyed by their existingcategory.markerKey, but each definition also carries a markerId in the custom:* namespace:
Profile category placement
importedData.markerPlacements stores optional display categories by immutable marker identity:
js/marker-placement.js owns migration, validation, resolution, and final projection. Moving a marker back to its native category removes the redundant assignment.
The placement engine accepts a destination only when:
- the destination category exists;
- the destination is not a calculated category;
- source and destination use the same time-series or
singlePointmode; and - the destination does not already reserve the same marker key for another identity.
Storage and display boundary
getActiveData() performs calculations, range application, and unit conversion at native storage locations. applyMarkerPlacements() is the final view projection. Each active marker then carries:
Any mutation must use
storageDotKey. Never reconstruct a persisted key from the category in which a marker is currently rendered. UI state that still uses underscore IDs must resolve through the marker-placement helpers before editing data.
Persistence and compatibility
Marker placement is additive profile metadata:
Values, reference overrides, marker notes, per-value notes, manual-value provenance, import snapshots, dashboard references, and historical entries continue to use their original storage locations. A category change therefore requires neither migration nor re-import.
Contributor workflow
For schema or identity changes:tests/marker-placement.test.js, tests/marker-placement-data.test.js, tests/marker-placement-editing.test.js, tests/custom-marker-identity.test.js, tests/custom-marker-export-boundaries.test.js, and tests/playwright/marker-placement-ui.spec.js.
See Data pipeline, Storage schema, and Sync and delta internals for the surrounding contracts.