example leaves too; plugins/ is documentation now

Done with `tea` as admin: plugins/offscale made public, plugins/example created
public, both verified anonymously over https — which is the check that matters,
because fetch.ts clones with no credentials and a private plugin simply cannot be
installed from the marketplace.

  plugins/example    8 files, cloned back and diffed identical
  plugins/offscale   now public (was private, so was uninstallable)

`.gitignore` is one line now — `/plugins/*/`. The trailing slash is the whole
design: it ignores DIRECTORIES, so files at the top of plugins/ stay tracked. The
documentation about plugins belongs to the platform; the plugins do not.

All three are in the marketplace catalogue, which is what makes this reversible:
before this, untracking offscale meant a fresh install could never get it back.
Now the store is the way in for all three.

CatalogueEntry gains `icon` (a lucide name) alongside `iconUrl`. Both new entries
ship glyphs rather than artwork, and without it they drew the generic puzzle piece
in the store and their real icon after install — which reads as the icon changing
rather than the store not knowing it.

EXTRACTING-A-PLUGIN.md pointed at four directories a fresh checkout no longer has.
It now gives repository URLs, says up front that this directory holds documentation
rather than plugins, and carries a table of where each one went. Checked first that
no tracked source statically imports a plugin directory — none does, so a fresh
clone still builds.

tsgo clean, frontend builds, 787 pass / 7 fail (unchanged).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 18:21:51 +00:00
co-authored by Claude Opus 5
parent cca85a7524
commit dd26e4a688
14 changed files with 104 additions and 172 deletions
+36 -7
View File
@@ -2,14 +2,24 @@
The runbook, written the day offscale became the first one. Follow it for music, then for the rest.
**This directory holds documentation, not plugins.** Every plugin is its own repository as of
2026-08-15, and arrives in `plugins/<name>/` by `git clone` when somebody installs it — so on a fresh
checkout the four references below are URLs, and on a machine where they are installed they are also
directories. Both are given.
**Read first, in this order:**
1. `plugins/offscale/PLUGIN.md` — every decision and why, including the three that reversed
2. `plugins/example/` — the reference implementation, deliberately the smallest real plugin
3. `plugins/offscale/` — the worked example, all four parts
4. `plugins/music/PLUGIN.md` — the MESSY worked example: three pieces that stayed behind, and why each
is a seam rather than a loose end. Read it if your feature has anything the platform also uses.
5. `src/servers/plugins/` — the system itself: `manifest`, `discover`, `mount`, `install`, `ecosystem`, `schema`, `generate`
1. [`plugins/offscale` → `PLUGIN.md`](https://gitea.officer.dev/plugins/offscale/src/branch/main/PLUGIN.md)
— every decision and why, including the three that reversed
2. [`plugins/example`](https://gitea.officer.dev/plugins/example) — the reference implementation,
deliberately the smallest thing that is still a real plugin
3. [`plugins/offscale`](https://gitea.officer.dev/plugins/offscale) — the worked example, all four parts
4. [`plugins/music` → `PLUGIN.md`](https://gitea.officer.dev/plugins/music/src/branch/main/PLUGIN.md)
— the MESSY worked example: three pieces that stayed behind, and why each is a seam rather than a
loose end. Read it if your feature has anything the platform also uses.
5. `src/servers/plugins/` — the system itself: `manifest`, `discover`, `mount`, `install`, `ecosystem`,
`schema`, `generate`. This one IS in this repository: the platform owns the plugin system, and only
the plugins left.
---
@@ -138,7 +148,8 @@ A normal refresh is enough; the shell is `no-store`. When the log's last line ap
## Music is done. What it changed about this runbook
Extracted 2026-08-15 and verified live through the whole table above. `plugins/music/PLUGIN.md` is the
Extracted 2026-08-15 and verified live through the whole table above.
[`plugins/music` → `PLUGIN.md`](https://gitea.officer.dev/plugins/music/src/branch/main/PLUGIN.md) is the
record; the parts worth carrying forward are already folded into the rules and traps above.
The one thing that generalises: **map what the PLATFORM still needs from your feature before you plan the
@@ -192,3 +203,21 @@ No decision has been made. What the tree says, for whoever picks it:
still there for plugins that have left. Generalising the union to `` `${string}:server` `` is the fix.
- **`hasPersonalWrites` reads `c.personal` only**, so a plugin declaring the same thing through
`readOnlyWrites` reports `false`. Nothing renders it, so it is dead on the wire.
---
## Where the plugins went
| Plugin | Repository | In this repo? |
| ---------- | ------------------------------------ | ------------- |
| `example` | `gitea.officer.dev/plugins/example` | no |
| `offscale` | `gitea.officer.dev/plugins/offscale` | no |
| `music` | `gitea.officer.dev/plugins/music` | no |
All three are public and clone anonymously over https, which is what the marketplace requires — it
clones with no credentials on purpose, so a private plugin cannot be installed from it at all.
`.gitignore` ignores `/plugins/*/` — directories only, so this file and anything beside it stay tracked.
The rule exists because a cloned plugin carries its own `.git`, and a tracked one turns `git add -A` into
a commit of a gitlink: a pointer to a commit this repository does not contain. That is not a mistake to
be careful about, it is what installing a plugin does, so it is handled by rule.