Three rules for work that spans plugins, in section 3 (Reuse library/ first):
- A component that could serve more than one plugin is built once in library/,
driven by props, and landed through the build owner, so other plugins adopt it
instead of copying it. This replaces "Graduate late".
- A new pattern starts on one plugin, built by its owner. A maintainer approves it on
the dev server, and only then do the other owners adopt it in their own plugins.
- Map controls are round and spaced: MapToolbar's default column of round
MapControlButtons, not its grouped square card. Dashboard header-bar buttons are
rounded squares with the vertical tabs' radius, var(--vtl-radius, 8px).
A fresh reader given only the README and HOWTO-join-the-table got through install, bun run dev and
the Studio. One gap: no address for a single plugin's page. The Capability 1 fold says each plugin's
folder holds "an index.html for previewing it", which reads as /plugin-designs/<plugin>/index.html,
and that 404s under bun run dev. The page is /__leaf/<plugin>/<profile>, and the Studio's list is
/__plugin-gallery/manifest.json. Both are now named.
Three smaller notes from the same reader:
- the README copied .env.example to .env while the HOWTO uses .env.local. Both work and .env.local
wins; the README now uses .env.local too, which also keeps .env free for compose.yaml;
- the Mapbox token read as required; it is one of two ways to get a background;
- the 8090 port hint (DX_PB_PORT) was only in the HOWTO; it is now named where the README first
mentions PocketBase.
Also, the engine rule (was a follow-up commit, f0e965e4d):
Since 2026-09-28 useMapProvider.js resolves `auto` to MapLibre, and Mapbox is an explicit choice:
"provider": "mapbox" plus a token. The docs still said "Mapbox when a token is available":
- README's map-engines row;
- how-to.md's provider section;
- the plugin-designer skill's props table, which even says to confirm in useMapProvider.js.
All three now state the current rule. So do four lines that told readers to set provider maplibre to
avoid needing a key: README's Get it running and its "No token" fold, .env.example's token comment,
and reference.md's no-key row.
Case-insensitive git grep "Mapbox when a token" over README.md, documentation/ and skills/: 3 before, 0 after.
THIRD-PARTY.md's two open licensing items "need an owner's decision" instead of going to a named person.
needs-eyes describes the browser policy and a maintainer's laptop as the second pair of eyes. The
skill and its glossary say the Experimental section was replaced by capability tiers, with the same
pointers into plugin-studio.vue. grep -w Driver over documentation/ and skills/ is now 0;
check:no-new-driver-references passes.
The joining page, DEPLOY and the laptop sync default moved to development in the
commit before this one. This finishes the move and closes what a fresh clone's
friend test found.
- landing-preconditions diffs against origin/development when no landing branch
is exported, instead of main.
- CLOUD-DEVELOPMENT-ENVIRONMENT, the plugin-designer skill (SKILL.md, workflow,
glossary) and the shared dev server's notes land on and follow development;
the skill says never to push to main.
- The sync fetches with the laptop's own credentials. git runs with prompts off,
so on a clone with no credential helper it failed every interval with a bare
"fetch-failed / remote: Unauthorized", which read as a broken server. That case
is now its own state, needs-credentials: the log (once, not every tick) and
/api/dev-sync say "dev-sync needs git credentials for <remote>". The joining
page and .env.example say so, and that a fresh clone starts on main and must
switch to development before the sync can fast-forward it.
Measured in scratch repos: the private remote with no helper gives
needs-credentials and one warning over two ticks; a local remote with no
DX_SYNC_BRANCH follows development and fast-forwards; a missing remote stays
fetch-failed.
skills/plugin-designer/references/i18n.md, beside backend/glossary/targets/workflow. Every example is
real code with a file and line, and every claim was checked against main rather than written from
memory — a reference that is wrong costs more than no reference.
★ ONE CORRECTION TO THE BRIEF, and it matters for whoever reads this next. The ask named poster-wall.vue
as the fleet's `<i18n-t>` example. It is the opposite: that file's comment explains why `<i18n-t>` could
NOT be used there — the string is a prop rendered by a library component with no slots — and there is no
`<i18n-t>` usage anywhere in the tree. So the rule is written as "compose the parts, do not reach for
i18n-t", which is what the code actually does and what a plugin author can actually follow. Telling them
to use a component the codebase has never used would have sent them down a path with no example to copy.
The rules: one key per SENTENCE with named placeholders (the Hindi tail case, where "." in English is
" प्रार्थना करते हुए।" in Hindi and a wrapper would have dropped it); label form for counts while there is
no pluralRules layer; compose markup from escaped parts; dir="ltr" for deliberate English so the gate can
tell it from a leak; te()/proxy-t fallbacks are NOT leaks and must not be "fixed"; no resolved labels at
module scope (getters, or the first locale freezes); a namespaced translator hides the prefix from every
scanner; and the two gates that cover your code rather than the catalogue.
Rule 8 carries the precision dx-shepherd gave me today: check:hardcoded-english is ON DEMAND, and
gates:pre runs only its self-test — so an author who assumes the suite covers it will land leaks.
skills/ is shared, so this is a source sha for studio rather than something I land.
A plugin's <profile>.meta.json targets (web, mobile, desktop, widget; absent = web)
now decide where it is built. internal/plugin-targets.mjs is the one reading.
desktop:build builds a desktop plugin into .desktop/<plugin>/ with a page that
names its engine, builds its backend as a Tauri sidecar for this machine, and runs
tauri build with a --config override; tauri.conf.json and public/ are untouched.
The shell starts the bundled rudder-engine (RUDDER_AUDIO=browser, RUDDER_HOME in the
app data folder) and stops it on exit. A plugin whose targets leave out web
(Rudder: desktop + widget) is no longer built for the web tree: run-parallel,
generate-tree and check-composite-freshness skip it, and its public/ folder is
removed; the dev server still gives the Studio its corner. check:targets
(gates:pre, with a self-test of five planted violations) validates the values,
widget corners and backend folders and fails if a non-web plugin reaches the built
tree. Measured: desktop:build rudder-voice on Linux produced Rudder_0.1.0_amd64.deb
(8.8 MB) with the app and the engine side by side; the app has not been launched.
SKILL.md claimed new plugins land in a Research Gallery Experimental section.
That section was replaced by capability tiers (plugin-studio.vue:630); stage is
now a label prefix, not a place. Both stale lines corrected.
Adds what the skill never said:
- capability is DERIVED from composes and an explicit type is CHECKED against it
(check-capability-derived.mjs), with its two traps;
- rail labels come from meta, and check-studio-rail.mjs:68 fails a bare id;
- presets merge OVER the profile's meta config, key by key -- rule 3 of
documentation/PRESET-PLUGIN-STANDARD.md, asserted by check:preset-config-merge
(generate-tree.mjs:1094, the comment there records the pinHint bug);
- targets is read (profile-meta.mjs:28) and consequential: the Research Gallery
skips a bundle that does not list web (doxa-maps-spa.vue:605).
Depth moves to references/: glossary, targets, backend, workflow. backend.md
carries the PocketBase user tiers marked DESIGN INTENT, NOT BUILT -- quoted
rather than linked, because their source lives outside this repo.
Verified against this tree, rebased on main: 61 path citations resolve, 10 line
citations land on the code they claim.
README: one line pointing an agent at the skill.
The old skill predated MapLibre, the universal props, the dx-maps tree and the three outputs.
plugin-designer teaches an agent to design a plugin in plugin-designs/ against the Plugin Studio:
bundle = folder = one JS file; the starter (now a real minimal map) and its exact renames; reuse
library/ first, private src/, library changes to the build owner; the universal props (engine
auto/mapbox/maplibre, locale, token, token service) with their channels in order; seven engine
rules, each from a real bug, including parsing expressions headless with
@maplibre/maplibre-gl-style-spec (a transitive dependency, so the skill says so); localization
with right to left on the element only; the five capability levels; learning from the seven
production plugins (each rule mapped to the one that shows it best); dev in the studio, the one
build, the three outputs. Reviewed by studio and exp-shepherd; every path it names exists on
main; references nothing outside this repo.