doxa-maps/documentation/architecture/color-strategies.md
doxa-maps-88655c70 9dc4dda8cf
Some checks are pending
build-gate / build (push) Waiting to run
consolidate docs into documentation/ and deployment shells into deployment/
Driver 2026-09-21: 'a folder called deployment which will store the capacitor
folder, wordpress folder and tauri folder', and 'a folder called documentation
to store the contributing folder so docs are in one place'.

docs/ + contributing/ + CONTRIBUTING.md -> documentation/ (three doc homes into
one). capacitor/ + wordpress/ + src-tauri/ -> deployment/.

The public/ -> dist/ rename the Driver asked about is NOT here: after seeing the
measured blast radius (855 tracked files, 54 source refs, and public/ is the
served web-root four consumers point at) the Driver withdrew it - 'keep the
public folder the same'.

Path references were rewritten mechanically (258 across 87 files), but three
classes needed hand fixes because a prefix regex cannot see them:
  - join(ROOT,'docs',...) in 10 internal scripts - a quoted SEGMENT, not a prefix
  - tauri frontendDist '../public' -> '../../public', since src-tauri is now one
    level deeper; check:tauri caught this exactly as the card predicted
  - the WordPress contract test's dirname(__DIR__,2) -> 3, same reason

Also reverted one over-reach: the rewrite renamed a FINDING-CATEGORY label
'docs' in public-hygiene-check.mjs, which is a rule name in its own header, not
a path.

Verified: full build ALL GUARDS PASS, SRI consistent; check:tauri, check:mobile
(cap sync + webDir OK), check:wp-embed, quoted-scripts, capability-citations,
capability-exemplars, library-index, public-hygiene, parse and drop all pass.
2026-09-21 23:23:23 +00:00

3.8 KiB

Architecture — color strategies (the full contract)

The simple overview is in library/colors/README.md. This is the complete contract for writing or overriding a color strategy.

A color strategy decides how people-group pins are colored for a given color mode (language family, prayer progress, religion, …). One strategy = one file. _registry.js auto-discovers every <name>.js via import.meta.glob and builds the COLOR_MODES enum + lookup from the filenames — no manual registration.

Filename → mode mapping: prayer-progress.js → prayerProgress / COLOR_MODES.PRAYER_PROGRESS; religion.js → religion / COLOR_MODES.RELIGION. Files starting with _ or . are skipped.

The strategy contract

Every <name>.js MUST export default this shape. A missing name (regression card #20) leaves the tab/legend label blank (the map still paints):

export const PROPERTY_KEY = 'myField'         // GeoJSON feature property this reads
export function getColor(properties) { ... }  // (props) -> '#rrggbb' — JS-side resolver
export function applyColor(options = {}) { ... } // -> Mapbox expression array (head = operator)
export const buildColorExpression = applyColor   // alias used by the tab-switch handler

export default {
  name:        'My Strategy',   // REQUIRED — human label, never omit
  propertyKey: PROPERTY_KEY,
  getColor, applyColor, buildColorExpression,
  // …any palette/labels you also want on the default export
}

applyColor() must return a valid Mapbox paint expression — an array whose first element is an operator ('match', 'case', 'interpolate', …). It feeds circle-color in useMapLayers.addLanguageFamilyLayer.

Add a custom strategy (checklist)

  1. Create <name>.js following the contract. Deriving color from a string? reuse generateColorFromString from ../utils/geoUtils.js (see doxa-region.js). The mode key is the camelCased filename — pick the filename to match the mode string consumers pass.
  2. Build — it auto-registers. No _registry.js edit. (Needs a export default with a non-empty name; files without a default export are ignored.)
  3. Make the pin feature carry your PROPERTY_KEY — pin properties are built in composables/useMapLayers.js → addLanguageFamilyLayer. Missing field → every pin falls to default.
  4. Wire the legend (only if the mode shows a legend tab) in composables/legends/useLegendData.js: add TITLE_KEYS, activeFilter, setFilter, and items branches. Religion is the worked example (reads PALETTE, RELIGION_FAMILIES, getReligionFamily via named exports, so export those too).
  5. Verify — the structural tooling resolves every mode and asserts a non-empty name, a valid expression array, and a hex getFeatureColor.

Profile-local strategies (override / add without touching the library)

library/colors/ is the shared set. A single map that wants its own coloring drops a <name>.js in that bundle's plugin-designs/<bundle>/src/colors/. At init, index.js calls registerStrategies(import.meta.glob('./src/colors/*.js', { eager: true })), merging local files over the shared set:

  • Same mode key as a shared strategy → local overrides it, for that bundle only.
  • New mode key → local adds a bundle-private strategy.

Each bundle is its own IIFE, so a local strategy can't leak to another map. This is the "local by default, promote deliberately" seam. See documentation/contributing/COLORS.md and plugin-designs/template-bundle/src/colors/ for the copy-me example.

Status / custom modes

COLOR_MODES.STATUS and COLOR_MODES.CUSTOM are declared but have no strategy file — getColorStrategy() falls back to LANGUAGE_FAMILY for any unknown mode. Add a real file (steps above) when one graduates from placeholder.