Writing a plugin
Tiago Águeda edited this page 2026-09-22 08:50:44 +02:00

Page revisions

14 Commits

Author SHA1 Message Date
6e3839b69c
Say that a plugin's mark may be a vector, and what it may not carry
*Writing a plugin* said "raster only", which was true and is not any
more (postulo/postulo#264). SVG is what a mark is authored as and what
the lists want at 24 pixels, so it is the preferred form now.

The section gains the part an author actually has to act on: the
sanitiser's rule, stated as a rule rather than as a list of refusals.
No external references of any kind -- `href` and `url()` may point at a
`#fragment` in the same file and nowhere else, and there is no `<style>`
element and no `style` attribute. Draw the mark, embed nothing.

It also says *why*, because the reason is not the obvious one: a plugin
already runs Python in the Postulo process, so a picture is not where
the danger is. The rule is there so a CDN reference in an exported SVG
cannot tell the plugin's author which instances run it -- which is the
promise the whole logo mechanism exists to keep, and which design tools
break by accident every day.

And the mark is no longer squared for you: it is kept at the shape it
was drawn, because every place it appears already draws it
`object-contain` in a square box.

Refs postulo/postulo#264

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 08:50:44 +02:00
e3d4d5ea89
Say what happens when an install goes wrong
*Getting a plugin into an instance* described what is refused before an
install and what is recorded after one, and nothing about the middle --
because until postulo/postulo#246 there was no middle: the wheel was
written over the working version and that was that.

There are three steps now, and an administrator reading this page is the
person who needs to know them: the directory is kept, the new plugin is
imported in a separate process before it is recorded, and a failure puts
everything back. Plus `plugins rollback`, for the upgrade that installed
cleanly and turned out to be wrong, and a note that there is one step
back rather than a chain.

Two sections gained the rest: what a removal takes with it, now that it
takes the dependencies nothing else is using; and what the constraint
covers, now that it covers the other plugins' pins as well as Postulo's
-- with a refusal that names the plugin holding the pin.

Refs postulo/postulo#246

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 18:43:40 +02:00
c98f88ca85
Teach the surface the page was already teaching past
Every example on *Writing a plugin* that reached past
`postulo.plugins.api` did so because the name it needed was not there: a
notifier's `Notification`, a sync's `SyncLink`, the `suggest` a mailbox
plugin files a guess with. Those names are on the surface now, so the
examples import them from it.

*Notifiers* gains the four fields `Notification` carries beside the
words -- what the message is, which language it came out in, when the
thing happened, and the thing itself in machine terms. *Syncs* gains the
table of what a sync may walk and what it may write, with the rule that
nothing is written by saving a model. The list of every name gains two
sections, and a paragraph saying why this half arrived late.

The section on what you may import gains the check itself:
`postulo.plugins.testing` is what Postulo runs over the plugins it
ships, and a plugin repository can now run it over its own source in
three lines.

One example is still past the surface -- a settings section is
registered through `postulo.core.settings_sections` -- and the page says
so rather than leaving a reader to find out from an `AttributeError`.

Refs postulo/postulo#229

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:17:12 +02:00
b814e6b839
Say how a plugin asks for the person, and name the Test bound
Refs postulo/postulo#232

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 10:58:16 +02:00
fc8a1a81d4
Write the importer contract, now that the group is open
The chapter said the contract was deliberately not written and to treat it as a
description rather than an invitation. It is the invitation now: the entry-point
group, the two methods, every key the writer reads from a record, what a date
and a proficiency are, what to raise, and how to test one. `Record` joins the
surface table.

Refs postulo/postulo#105

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 10:37:08 +02:00
d4eb5c32a3
Tell plugin authors about components and the button vocabulary
A plugin page drawn with `<c-field>` and `class="btn"` looks like the rest of the
settings; before this the page said nothing about how to draw one.

Refs postulo/postulo#263, postulo/postulo#262

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 23:52:51 +02:00
5efcbca6f6
Writing a plugin: the public-only client, and saying a connection is finished
public_only_client is on the surface for what is public by definition -- a posting, a portfolio, a logo, a push service -- and refuses a private address whatever the operator allowed for connections. ConnectionUnusable is how a plugin says the other side has ended it: Postulo switches the connection off, shows the reason and forgets the dead credential, instead of failing identically every time.

Refs postulo/postulo#216

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 08:52:06 +02:00
5f2ece6e41
Writing a plugin: dialling something that is not HTTP
A plugin with a socket of its own -- a mailbox, a queue -- had no way to ask where it may dial. approve_host, check_destination and DestinationRefused are on the surface now: resolve the name, hold every address it answers with to the instance's policy, dial the one that comes back, and prove the certificate against the name.

Refs postulo/postulo#215

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 08:34:41 +02:00
cce3ba6128
Configuration, Writing a plugin: the Browser notifier and form_attributes()
Configuration gains "In the browser" under Notifications: the two ways a
browser notification arrives, which third party the push way involves and
what it can and cannot read, what the notifier needs (HTTPS, scripts,
outbound HTTPS, the scheduler), and why rotating the field key ends every
subscription. Writing a plugin documents the optional form_attributes()
method, and why a plugin still cannot ship JavaScript of its own.

Refs postulo/postulo#209

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 21:09:31 +02:00
a9b6ec567e
Say that what Postulo ships is an administrator's to switch, and hidden on Settings → Plugins until asked
Refs postulo/postulo#200

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:33:57 +02:00
53cda5b0a3
Tell a plugin to make its catalogues with postulo-messages, and to gate them at a release
Refs postulo/postulo#187

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 05:58:13 +02:00
8e3f29723e
Say that requires_postulo is enforced, and how official plugins are versioned
Refs postulo/postulo#186

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 05:44:03 +02:00
49734a5c4c
Describe the three link features, and count the shipped plugins again
*Tracking applications* gains "More than one link" beside "More than one number": three
plugins over one table, the primary of each kind on a CV, the kind naming the sort of thing
and never the host, and the promise that switching one off deletes nothing. *Writing a
plugin* lists the three new packages in the tree, says fourteen across eight kinds, and
names every feature Postulo ships rather than the two it named when there were two.

Refs postulo/postulo#189

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 22:02:44 +02:00
d95b27d240
Document the whole API surface: every call, the machine endpoints, the plugin API
The API named every call, but the scope each needs lived in prose, and
the discard reasons and what /me answers were not written anywhere. It
gains both, and one list of all 39 calls with method, path and scope,
generated from the API's own routers.

/healthz, /metrics and /logs answer machines about the instance and
were only rows in Configuration. Health, metrics and logs says what
each returns, how it is switched on and guarded, the rate limit, the
parameters /logs takes, and every metric with its labels.

The plugin guide was postulo's docs/PLUGINS.md and never reached the
wiki. It is Writing a plugin now, opening with every kind of plugin,
its entry-point group and the interface it satisfies, and carrying a
reference of all 37 names postulo.plugins.api promises -- eight of
which the guide had never named. It also says plainly that a
notifier's Notification, and the client's DestinationRefused, are used
from outside the surface today.

The sidebar gains a Building on it group for the API and the plugin
guide, and the three pages that linked to docs/PLUGINS.md link here.
postulo's tests/test_wiki_surface.py now reads these three pages
against the code.

Refs postulo/postulo#170

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:36:50 +02:00