Writing a plugin: three notifier events of seven, a logo URL the code refuses, stale counts and docstrings #405

Closed
opened 2026-10-01 10:46:27 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d and postulo.wiki at de1f52467, reading the plugin author's documentation against postulo.plugins and notifications/base.py. Related to #106 and #264 (both closed), which made a plugin's logo a file inside its package, served as sanitised SVG or a re-encoded PNG; this issue lists the documentation and docstrings those two left describing the earlier design, and the other places where the same wiki page has fallen behind the code.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The consent example in CONTRIBUTING.md is now at :560 (the import) and :584 (postulo.plugins.consent.access_token).

The page a plugin author works from, Writing a plugin, disagrees with the code in six places, and the docstrings beside the logo code disagree with it in four more. Two of the wiki's errors produce a plugin that does the wrong thing without any error the author sees: a notifier written from the event list, and a manifest copied from the example.

1. The notifier events

Writing-a-plugin.md:404-405 says EVENTS "is every one of them: reminder_due, capture_received, went_quiet". notifications/base.py:29-37 defines seven: those three plus posting_closing, status_changed, interview_scheduled and offer_recorded. A notifier that switches on notification.event from the documented list drops four kinds.

2. The logo in the manifest example

Writing-a-plugin.md:1045 gives logo="https://example.org/your-plugin/logo.png", and :1062 says "logo names an image to show beside your plugin's name. Nothing renders it yet." The page's own A logo, if you have one (:881 onwards) says the opposite: a file name inside the package, served by Postulo, and "There is no way to give a URL". The code agrees with that section: raw_bytes (plugins/logos.py:105-106) refuses any name holding a separator or starting with a dot ("a logo is a file name, not a path"), logo_for turns the refusal into the initials tile, and the only trace is a warning in the administrator's log. An author who copies the example gets no logo and no explanation.

3. The logo's size limit

Writing-a-plugin.md:906 says "Under 5 MB in, under 1 MB stored." MAX_BYTES (plugins/logos.py:45) is 2 * 1024 * 1024, checked in raw_bytes (plugins/logos.py:115-116) before anything is decoded, and an SVG is further held to MAX_SVG_BYTES, 512 KiB (core/pictures.py:244, :297). A 3 MB raster file, allowed by the page, is refused the same silent way as the URL above. Only "under 1 MB stored" matches (MAX_STORED_BYTES, plugins/logos.py:54).

4. The logo docstrings still describe the design before #106 and #264

  • plugins/base.py:174-176 (Manifest): "Nothing renders it yet; the field exists so that shipping one (#106) is a matter of pointing at an image". It is rendered, and it is a file name in the package.
  • plugins/logos.py:21-25 (module docstring): "Raster only … The bytes are decoded and written out again as PNG". _render (plugins/logos.py:145-169) keeps an SVG as a sanitised SVG and says SVG "is the preferred form".
  • plugins/logos.py:47: "What a resolved logo is: the PNG bytes, and the plugin they belong to." The comment directly under it (:48-50) says the cache holds bytes and a content type since #264; the first line was left behind.
  • plugins/logos.py:121 (logo_for): "The logo as a square PNG". It returns (bytes, content type), the type may be image/svg+xml, and _render calls pictures.as_stored without square=True, so a raster mark keeps its own shape.

5. The shipped plugins, counted three ways

plugins/ holds seventeen plugin packages, and each calls shipped(). Writing-a-plugin.md:842 says "Fourteen of them across eight kinds", and its tree (:846-859) omits browser/, gdpr/ and webhook/. :1097 says of the plugins that use shipped(): "There are six of them". docs/PLAN.md:181 says "fifteen", and its tree (:184-186) omits gdpr/ and webhook/.

6. Installing from the interface

Writing-a-plugin.md:1536-1537 says "Installing from the interface — an uploaded package, a catalogue — is a later step on the roadmap", while :1357 (From the interface) describes exactly that on Server settings → Plugins.

CONTRIBUTING.md:558 has from postulo.plugins.base import Consent, FieldSpec, and :582 sends the reader to postulo.plugins.consent.access_token(connection). Official plugins may import only postulo.plugins.api, which exports all three names (plugins/api.py:152, :159, :184); test_a_shipped_plugin_imports_the_surface_or_something_written_down in tests/test_plugin_surface.py fails a shipped plugin that follows the example, and the published check in postulo.plugins.testing does the same for a plugin written elsewhere.

  • Where: postulo.wiki Writing-a-plugin.md:404-405, :842-860, :906, :1045, :1062, :1097, :1536-1537; docs/PLAN.md:181-186; CONTRIBUTING.md:558, :582; plugins/base.py:174-176; plugins/logos.py:21-25, :47, :121.
  • Why it matters: the wiki is the plugin author's contract. The event list and the manifest example produce plugins that silently miss four kinds of notification or show no logo, the size limit promises more than the code accepts, the consent example teaches the import the project forbids, and the docstrings tell the next person to change logos.py that SVG is refused when it is the preferred form.
  • Fix:
    • Events: refer to EVENTS rather than enumerating it, or list all seven with their labels.
    • Manifest example: change it to logo="logo.svg" and replace the sentence at :1062 with a pointer to A logo, if you have one.
    • Size: make :906 say 2 MB for a raster file and 512 KB for an SVG (or raise MAX_BYTES to match the page, deliberately).
    • Docstrings: rewrite the logos.py module docstring, the comment at :47 and the logo_for docstring to describe a sanitised SVG or a re-encoded PNG at its own shape, and the Manifest docstring to say the logo is a file name in the package, served by Postulo.
    • Counts: correct the numbers and both trees, or drop the numbers, which are what keeps going stale.
    • Delete the roadmap sentence at :1536-1537.
    • CONTRIBUTING.md: from postulo.plugins.api import Consent, FieldSpec, access_token, and call access_token(connection).

Done when

  • tests/test_wiki_surface.py checks that every key of notifications.base.EVENTS appears on Writing a plugin, that every plugin package under plugins/ appears in its tree, and that no logo= on the page holds a separator; all three fail today.
  • The size limits stated in A logo, if you have one equal MAX_BYTES and MAX_SVG_BYTES.
  • test_a_logo_is_a_file_name_and_never_a_path in tests/test_plugin_logos.py includes a URL among its names, so the refusal the wiki documents stays true.
  • No docstring in plugins/base.py or plugins/logos.py says "Nothing renders it yet", "Raster only" or "square PNG", and CONTRIBUTING.md imports Consent, FieldSpec and access_token from postulo.plugins.api.
Found on 2026-09-29 in a read-only review of postulo at 95348908d and postulo.wiki at de1f52467, reading the plugin author's documentation against `postulo.plugins` and `notifications/base.py`. Related to #106 and #264 (both closed), which made a plugin's logo a file inside its package, served as sanitised SVG or a re-encoded PNG; this issue lists the documentation and docstrings those two left describing the earlier design, and the other places where the same wiki page has fallen behind the code. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The consent example in `CONTRIBUTING.md` is now at `:560` (the import) and `:584` (`postulo.plugins.consent.access_token`). The page a plugin author works from, *Writing a plugin*, disagrees with the code in six places, and the docstrings beside the logo code disagree with it in four more. Two of the wiki's errors produce a plugin that does the wrong thing without any error the author sees: a notifier written from the event list, and a manifest copied from the example. ## 1. The notifier events `Writing-a-plugin.md:404-405` says `EVENTS` "is every one of them: `reminder_due`, `capture_received`, `went_quiet`". `notifications/base.py:29-37` defines seven: those three plus `posting_closing`, `status_changed`, `interview_scheduled` and `offer_recorded`. A notifier that switches on `notification.event` from the documented list drops four kinds. ## 2. The logo in the manifest example `Writing-a-plugin.md:1045` gives `logo="https://example.org/your-plugin/logo.png"`, and `:1062` says "`logo` names an image to show beside your plugin's name. Nothing renders it yet." The page's own *A logo, if you have one* (`:881` onwards) says the opposite: a file name inside the package, served by Postulo, and "There is no way to give a URL". The code agrees with that section: `raw_bytes` (`plugins/logos.py:105-106`) refuses any name holding a separator or starting with a dot ("a logo is a file name, not a path"), `logo_for` turns the refusal into the initials tile, and the only trace is a warning in the administrator's log. An author who copies the example gets no logo and no explanation. ## 3. The logo's size limit `Writing-a-plugin.md:906` says "Under 5 MB in, under 1 MB stored." `MAX_BYTES` (`plugins/logos.py:45`) is `2 * 1024 * 1024`, checked in `raw_bytes` (`plugins/logos.py:115-116`) before anything is decoded, and an SVG is further held to `MAX_SVG_BYTES`, 512 KiB (`core/pictures.py:244`, `:297`). A 3 MB raster file, allowed by the page, is refused the same silent way as the URL above. Only "under 1 MB stored" matches (`MAX_STORED_BYTES`, `plugins/logos.py:54`). ## 4. The logo docstrings still describe the design before #106 and #264 - `plugins/base.py:174-176` (`Manifest`): "Nothing renders it yet; the field exists so that shipping one (#106) is a matter of pointing at an image". It is rendered, and it is a file name in the package. - `plugins/logos.py:21-25` (module docstring): "**Raster only** … The bytes are decoded and written out again as PNG". `_render` (`plugins/logos.py:145-169`) keeps an SVG as a sanitised SVG and says SVG "is the preferred form". - `plugins/logos.py:47`: "What a resolved logo is: the PNG bytes, and the plugin they belong to." The comment directly under it (`:48-50`) says the cache holds bytes and a content type since #264; the first line was left behind. - `plugins/logos.py:121` (`logo_for`): "The logo as a square PNG". It returns `(bytes, content type)`, the type may be `image/svg+xml`, and `_render` calls `pictures.as_stored` without `square=True`, so a raster mark keeps its own shape. ## 5. The shipped plugins, counted three ways `plugins/` holds seventeen plugin packages, and each calls `shipped()`. `Writing-a-plugin.md:842` says "Fourteen of them across eight kinds", and its tree (`:846-859`) omits `browser/`, `gdpr/` and `webhook/`. `:1097` says of the plugins that use `shipped()`: "There are six of them". `docs/PLAN.md:181` says "fifteen", and its tree (`:184-186`) omits `gdpr/` and `webhook/`. ## 6. Installing from the interface `Writing-a-plugin.md:1536-1537` says "Installing from the interface — an uploaded package, a catalogue — is a later step on the roadmap", while `:1357` (*From the interface*) describes exactly that on *Server settings → Plugins*. ## 7. The consent example imports past the surface `CONTRIBUTING.md:558` has `from postulo.plugins.base import Consent, FieldSpec`, and `:582` sends the reader to `postulo.plugins.consent.access_token(connection)`. Official plugins may import only `postulo.plugins.api`, which exports all three names (`plugins/api.py:152`, `:159`, `:184`); `test_a_shipped_plugin_imports_the_surface_or_something_written_down` in `tests/test_plugin_surface.py` fails a shipped plugin that follows the example, and the published check in `postulo.plugins.testing` does the same for a plugin written elsewhere. - **Where:** `postulo.wiki` `Writing-a-plugin.md:404-405`, `:842-860`, `:906`, `:1045`, `:1062`, `:1097`, `:1536-1537`; `docs/PLAN.md:181-186`; `CONTRIBUTING.md:558`, `:582`; `plugins/base.py:174-176`; `plugins/logos.py:21-25`, `:47`, `:121`. - **Why it matters:** the wiki is the plugin author's contract. The event list and the manifest example produce plugins that silently miss four kinds of notification or show no logo, the size limit promises more than the code accepts, the consent example teaches the import the project forbids, and the docstrings tell the next person to change `logos.py` that SVG is refused when it is the preferred form. - **Fix:** - Events: refer to `EVENTS` rather than enumerating it, or list all seven with their labels. - Manifest example: change it to `logo="logo.svg"` and replace the sentence at `:1062` with a pointer to *A logo, if you have one*. - Size: make `:906` say 2 MB for a raster file and 512 KB for an SVG (or raise `MAX_BYTES` to match the page, deliberately). - Docstrings: rewrite the `logos.py` module docstring, the comment at `:47` and the `logo_for` docstring to describe a sanitised SVG or a re-encoded PNG at its own shape, and the `Manifest` docstring to say the logo is a file name in the package, served by Postulo. - Counts: correct the numbers and both trees, or drop the numbers, which are what keeps going stale. - Delete the roadmap sentence at `:1536-1537`. - `CONTRIBUTING.md`: `from postulo.plugins.api import Consent, FieldSpec, access_token`, and call `access_token(connection)`. ## Done when - `tests/test_wiki_surface.py` checks that every key of `notifications.base.EVENTS` appears on *Writing a plugin*, that every plugin package under `plugins/` appears in its tree, and that no `logo=` on the page holds a separator; all three fail today. - The size limits stated in *A logo, if you have one* equal `MAX_BYTES` and `MAX_SVG_BYTES`. - `test_a_logo_is_a_file_name_and_never_a_path` in `tests/test_plugin_logos.py` includes a URL among its names, so the refusal the wiki documents stays true. - No docstring in `plugins/base.py` or `plugins/logos.py` says "Nothing renders it yet", "Raster only" or "square PNG", and `CONTRIBUTING.md` imports `Consent`, `FieldSpec` and `access_token` from `postulo.plugins.api`.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:46:27 +00:00
Author
Owner

Fixed in 54aeedaf9 on main.

Fixed in 54aeedaf9 on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#405
No description provided.