From 0ad2f033d1fb5410f85c165ef79a2e81971481c3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 15 Oct 2026 23:56:51 +0200 Subject: [PATCH] feat: give the plugin a registry icon The registry showed a letter placeholder: the manifest declared screenshots but no icon. images/icon.png (256x256, 8.6 KB) is rendered from images/icon.source.html in the banner's language: base-50 ground, faint ink waves, EmDash's gradient on a rising traffic line. It is declared under release.artifacts.icon, so it shows from the next published release on. Checked against EmDash 1.1.0, 1.2.0 and 1.3.0 with scripts/compat-matrix.sh: 314 of 314 tests pass on each, no code change needed. From 1.2.0 plugin:activate also fires for plugins: [] installs, so the comments that said it never does now name the version; the fallback scheduling stays for the 1.0.1 floor. The activate hook cannot throw, which matters because 1.2.0 disables a plugin whose activate hook throws. Patch changeset: 0.3.2. --- .changeset/registry-icon.md | 5 ++++ emdash-plugin.jsonc | 4 +++ images/icon.png | Bin 0 -> 8585 bytes images/icon.source.html | 58 ++++++++++++++++++++++++++++++++++++ src/index/bootstrap.ts | 4 +-- src/plugin.ts | 10 ++++--- src/sync/scheduler.ts | 12 ++++---- 7 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 .changeset/registry-icon.md create mode 100644 images/icon.png create mode 100644 images/icon.source.html diff --git a/.changeset/registry-icon.md b/.changeset/registry-icon.md new file mode 100644 index 0000000..adce111 --- /dev/null +++ b/.changeset/registry-icon.md @@ -0,0 +1,5 @@ +--- +"@eisbachcode/emdash-plugin-analytics": patch +--- + +The plugin now has its own icon in the EmDash registry, in search results and on its page, instead of the letter placeholder. Also tested on EmDash 1.1.0, 1.2.0 and 1.3.0; none needed a change. diff --git a/emdash-plugin.jsonc b/emdash-plugin.jsonc index ea43695..083dfbc 100644 --- a/emdash-plugin.jsonc +++ b/emdash-plugin.jsonc @@ -18,6 +18,10 @@ "release": { "requires": { "env:emdash": ">=1.0.1" }, "artifacts": { + // Uploaded into each release record, not the profile, so it only + // shows from the next published version on. 256x256, rendered + // from icon.source.html. + "icon": { "file": "images/icon.png" }, "screenshots": [ { "file": "images/analytics-page.png", "lang": "en" }, { "file": "images/per-entry.png", "lang": "en" }, diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..60574214e51d480172502f9bd8ffdf9fc8b53d4c GIT binary patch literal 8585 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 A1poj5 literal 0 HcmV?d00001 diff --git a/images/icon.source.html b/images/icon.source.html new file mode 100644 index 0000000..12992e3 --- /dev/null +++ b/images/icon.source.html @@ -0,0 +1,58 @@ + + + + + + + + + + + diff --git a/src/index/bootstrap.ts b/src/index/bootstrap.ts index 102347d..41d8f50 100644 --- a/src/index/bootstrap.ts +++ b/src/index/bootstrap.ts @@ -10,8 +10,8 @@ * Two things keep this cheap: * * - **It rides on the sync tick** rather than on `plugin:activate`, which - * is dispatched only by the admin Enable endpoint and never fires for a - * site that registers the plugin through `plugins: []`. + * fires once per activation (and, before EmDash 1.2.0, never for a site + * that registers the plugin through `plugins: []`). * - **It walks a few entries per tick.** `getPublicUrl()` is one bridge * call per entry and a sandboxed invocation gets ten in total, so the * page size is deliberately tiny and the pass spreads over many ticks. diff --git a/src/plugin.ts b/src/plugin.ts index c73aec8..f08fd76 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -63,9 +63,11 @@ const CRON_TIMEOUT_MS = 30_000; const plugin: SandboxedPlugin = { hooks: { /** - * Fires only when an administrator clicks Enable. A site that - * registers the plugin through `plugins: []` never gets one, which is - * why this is not the only place scheduling happens. + * Fires when an administrator clicks Enable, and from EmDash 1.2.0 also + * on the first start of a site that registers the plugin through + * `plugins: []`. Before 1.2.0 such a site never gets one, which is + * why this is not the only place scheduling happens. It must not + * throw: from 1.2.0 a throwing activate hook disables the plugin. */ "plugin:activate": async (_event, ctx) => { await scheduleFromSettings(ctx); @@ -101,7 +103,7 @@ const plugin: SandboxedPlugin = { errorPolicy: "continue", handler: async (event, ctx) => { // Also the reliable scheduling entry point for `plugins: []` - // installs, where `plugin:activate` never fires. + // installs before EmDash 1.2.0, where `plugin:activate` never fires. await scheduleFromSettings(ctx); await index(ctx, event, "published"); }, diff --git a/src/sync/scheduler.ts b/src/sync/scheduler.ts index 58dcaca..214ffac 100644 --- a/src/sync/scheduler.ts +++ b/src/sync/scheduler.ts @@ -162,12 +162,12 @@ export interface SyncOutcome { * Schedule both tasks, idempotently. * * Called from `plugin:activate`, from the content hooks and from the - * widget's first `page_load` — because `plugin:activate` is dispatched - * only by the admin Enable endpoint, and a plugin registered through - * `plugins: []` in the site config never gets one. That is the npm - * channel, which is the channel most installs use. `CronAccess.schedule` - * upserts on (plugin, task), so calling it repeatedly is free of - * consequence. + * widget's first `page_load` — because before EmDash 1.2.0 + * `plugin:activate` is dispatched only by the admin Enable endpoint, and a + * plugin registered through `plugins: []` in the site config never gets + * one. That is the npm channel, which is the channel most installs use. + * `CronAccess.schedule` upserts on (plugin, task), so calling it repeatedly + * is free of consequence. */ export async function ensureScheduled(ctx: PluginContext, interval: string): Promise { if (!ctx.cron) return;