SPEC-LIBRARY-CURATION.md's first migration, in the order the shepherd approved.
Source only; studio rebuilds (rule 5B — library/ plus eight other owners' plugins).
THE ASYMMETRY, which is why 12 sites each solved it or failed at it separately:
Mapbox getProjection() -> { name } setProjection('mercator')
MapLibre getProjection() -> { type } | undefined setProjection({ type: 'mercator' })
Passing the Mapbox shape to MapLibre THROWS. Measured on regions-dashboard, where
the throw aborted the camera fly so a region dashboard never framed its region.
WHAT THE 12 SITES WERE DOING:
· NINE passed a bare string, so on MapLibre the projection silently never changed.
Eight swallowed the throw in a try/catch; doxa-simple-map.vue:1244 did NOT, so
there it threw uncaught.
· TWO had solved it independently and differently: pplr-data-maps'
setProjectionFor() (instance-tested — the correct one, and the shape adopted)
and regions-dashboard's local pair (config-derived, which is weaker: it trusts
what the page was configured with rather than what the map IS).
· ONE probed by exception, trying the object form and falling back on throw.
The helper reads the engine from the INSTANCE, never from config, because a page can
hold a map built by the other engine. Both functions return something observable —
setProjectionOn returns whether it applied, projectionOf returns null rather than
throwing — on upg100-plugin's argument that a precondition which bails without
recording it is indistinguishable from the request never arriving.
pplrData.setProjectionFor stays as a delegating named export so its three views need
no change.
GATE: _chk-c2-projection-helper-equivalence.mjs, 20 assertions. It runs all THREE old
patterns and the helper against the same stub maps and compares outcomes, rather than
testing the helper alone. Controls prove pattern A really does fail on MapLibre and
leave the projection untouched, and that the bare string really throws — so the
"equivalence" claim has something to be equivalent to. A NEG control catches a helper
that reports success it did not achieve.
★ I DESTROYED 984 LINES OF ANOTHER TEAM'S CODE GETTING HERE, and only the build caught
it. My import-inserting script did `s[:at] + IMPORT` and never appended `s[at:]`, so
every file where the import was ADDED rather than extended was truncated at its last
import line: MapCell.vue -458, FlowerClusters.vue -334, lang-cluster.vue -192.
check:no-free-identifiers PASSED on all three, because a truncated script block is
still valid script. Restored from HEAD and redone with an assertion that a migration
must change the line count by exactly +1 and never remove a line — which is the check
that would have caught it in the first second.
Verified after: all 9 affected bundles build, both gates green, and a per-file line
delta on all 13 changed files with the only negative being the 2 lines I deliberately
deleted from RegionCountryMap when its local pair became an import.
Moved out (kept outside the repo, in the team's internal tracker):
- AGENT-BRIEF.md: operating rules for the team's own agents
- documentation/slide-deck-outline.md, documentation/README-longform.md:
internal pitch material; the README and why.md cover the same ground
- plugin-designs/gospel-resources/SOURCES-CHECKED-AND-REJECTED.md: research log
- .claude/ (an editor status line), .forgejo/ (a CI workflow that never ran),
_archive/ (a retired staging page; already gitignored but still tracked)
Seven code comments that named these files now describe the rule instead.
Fixed five dead links left from the docs/ -> documentation/ move, including
the one in template-bundle that every new plugin inherits.
The delegation diagram now shows the whole model: the Plugin Studio with the
shared library and the merge queue into main, seven production plugins with
capability badges and the Experimental shelf, one agent per plugin (a roaming
agent for the shelf), the shepherd's oversight ring, a legend, and the five
ways a build ships.
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.