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>
*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>
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>
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>
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>
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>
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>
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>
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>
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>