flare-plugin-sdk 0.0.0-stage → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +131 -2
  3. package/package.json +33 -4
  4. package/skill/flare-plugin/ABI.md +334 -0
  5. package/skill/flare-plugin/README.md +204 -0
  6. package/skill/flare-plugin/SKILL.md +80 -0
  7. package/skill/flare-plugin/assets/github-template/README.md +71 -0
  8. package/skill/flare-plugin/assets/github-template/assets/icon.png +0 -0
  9. package/skill/flare-plugin/assets/github-template/manifest.json +29 -0
  10. package/skill/flare-plugin/assets/github-template/plugin.js +108 -0
  11. package/skill/flare-plugin/examples/pixelfed/README.md +78 -0
  12. package/skill/flare-plugin/examples/pixelfed/assets/icon.png +0 -0
  13. package/skill/flare-plugin/examples/pixelfed/locales/en.json +24 -0
  14. package/skill/flare-plugin/examples/pixelfed/locales/zh-Hans.json +24 -0
  15. package/skill/flare-plugin/examples/pixelfed/manifest.json +211 -0
  16. package/skill/flare-plugin/examples/pixelfed/plugin.js +793 -0
  17. package/skill/flare-plugin/flare-plugin.d.ts +501 -0
  18. package/skill/flare-plugin/pack.mjs +155 -0
  19. package/skill/flare-plugin/references/authentication.md +239 -0
  20. package/skill/flare-plugin/references/capabilities.md +88 -0
  21. package/skill/flare-plugin/references/manifest.md +192 -0
  22. package/skill/flare-plugin/references/publishing.md +111 -0
  23. package/skill/flare-plugin/references/recipes.md +328 -0
  24. package/skill/flare-plugin/references/runtime-http.md +207 -0
  25. package/skill/flare-plugin/references/testing.md +176 -0
  26. package/skill/flare-plugin/references/troubleshooting.md +116 -0
  27. package/skill/flare-plugin/references/wire-data.md +163 -0
package/README.md CHANGED
@@ -1,3 +1,132 @@
1
- # Temporary Holding Version
1
+ # Flare Plugin SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `flare-plugin-sdk` contains editor types, a local package command,
4
+ templates, examples, and a portable AI authoring skill for Social Plugin API v1.
5
+ Flare provides the plugin runtime. Supported targets are Android, JVM Desktop,
6
+ iOS, and native macOS, not Web. Use a Flare build that includes Plugins in Settings;
7
+ this SDK does not promise a minimum public app release.
8
+
9
+ ## Install for development
10
+
11
+ Install the npm preview:
12
+
13
+ ```sh
14
+ npm install --save-dev flare-plugin-sdk@next
15
+ ```
16
+
17
+ To validate a Flare checkout without publication, build a local tarball instead:
18
+
19
+ ```sh
20
+ cd feature/plugin/sdk
21
+ npm ci
22
+ npm test
23
+ npm pack --pack-destination /tmp
24
+ ```
25
+
26
+ Install the resulting `.tgz` in your plugin project with
27
+ `npm install --save-dev /path/to/flare-plugin-sdk-VERSION.tgz`.
28
+ These local checks and packaging commands do not publish anything.
29
+
30
+ ## Create and develop a plugin
31
+
32
+ 1. Copy the contents of `node_modules/flare-plugin-sdk/skill/flare-plugin/assets/github-template/`
33
+ into your plugin repository. Replace its IDs, icon, and illustrative website
34
+ endpoints using evidence from the real API.
35
+ 2. Read the [complete developer guide](https://github.com/DimensionDev/Flare/tree/feature/plugin/feature/plugin/sdk/skill/flare-plugin).
36
+ The same guide is bundled at `skill/flare-plugin/README.md` inside the npm package,
37
+ with all authentication, capability, runtime, data, testing, and publication references.
38
+ 3. Keep your ready-to-run `manifest.json`, classic-script `plugin.js`, one supported
39
+ `assets/icon.<extension>`, and optional `locales/*.json` in a plugin output directory.
40
+ For a TypeScript project, configure your bundler to produce that classic script.
41
+ 4. In Desktop or native macOS, select the output directory in Settings → Plugins →
42
+ Plugin development. Start development, then reload after edits or rebuilds.
43
+ 5. For local file installation, package that directory from your plugin project:
44
+
45
+ ```sh
46
+ npx flare-plugin-pack ./dist ./plugin.fpp
47
+ ```
48
+
49
+ The packer accepts plugin files only, so keep source, `.git`, `package.json`,
50
+ `node_modules`, and the generated `.fpp` outside `dist`. It checks archive structure;
51
+ Flare still validates the manifest, registration, permissions, and runtime behavior.
52
+ For GitHub installation, commit the ready-to-run plugin files at the repository
53
+ root and publish a stable tag matching `manifest.version`. npm SDK versions and
54
+ individual plugin versions are independent; GitHub plugin installation ignores
55
+ prerelease tags even though the npm SDK can use them.
56
+
57
+ ## Editor types
58
+
59
+ Add the package to your TypeScript project's compiler options:
60
+
61
+ ```json
62
+ {
63
+ "compilerOptions": {
64
+ "types": ["flare-plugin-sdk"]
65
+ }
66
+ }
67
+ ```
68
+
69
+ Append it to any existing `types` list. For JavaScript, enable `checkJs` as well.
70
+ The declarations provide ambient `definePlugin`, `flare`, and wire types. Do not
71
+ import the SDK at runtime: Flare supplies those globals, and plugin scripts must
72
+ bundle their own runtime dependencies without Node or browser APIs.
73
+
74
+ ## Use the AI authoring skill
75
+
76
+ The self-contained skill is in `skill/flare-plugin/` in both the source and npm
77
+ package. Copy that **entire directory** into your agent's skill directory; copying
78
+ only `SKILL.md` loses its types, references, templates, and examples. Follow the
79
+ [skill installation guide](https://github.com/DimensionDev/Flare/blob/feature/plugin/feature/plugin/sdk/skill/flare-plugin/README.md#install-the-skill)
80
+ for Codex or Claude Code, including project scope, verification, and updates.
81
+ All documentation is in English. The complete Pixelfed adapter is bundled at
82
+ `skill/flare-plugin/examples/pixelfed/`.
83
+
84
+ ## Validate this SDK
85
+
86
+ From `feature/plugin/sdk`, run `npm ci` and `npm test` with Node 22 or newer.
87
+ The tests build an npm tarball, check its complete resource set and license,
88
+ install it offline in an empty project, type-check an actual consumer, and run
89
+ the installed package command against the bundled template. They do not contact
90
+ a website or publish to npm. Flare's Host and platform acceptance checks are
91
+ documented in `skill/flare-plugin/references/testing.md`.
92
+
93
+ ## Publish this SDK (maintainers)
94
+
95
+ For the first publication, use the npm account that will own this unscoped package.
96
+ Subsequent publication and Trusted Publisher setup require package maintainer access;
97
+ GitHub repository permissions do not grant npm access. The package includes the
98
+ repository's AGPL license; its types, templates, and skill remain the canonical source resources.
99
+
100
+ For the first publication, an authorized maintainer can use an npm account with
101
+ 2FA, run `npm ci` and `npm test` in this directory, then explicitly run:
102
+
103
+ ```sh
104
+ npm login
105
+ npm whoami
106
+ npm publish --access public --tag next
107
+ ```
108
+
109
+ Only run that last command when publication is intended. The GitHub workflow does
110
+ not bootstrap a new npm package or configure npm account permissions.
111
+
112
+ After the package exists, configure [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/)
113
+ in its npm Settings with GitHub owner `DimensionDev`, repository `Flare`, workflow
114
+ filename `plugin-sdk.yml`, no environment, and permission to publish directly with
115
+ `npm publish`. Use a GitHub-hosted runner. The workflow uses Node 24 and npm's OIDC
116
+ authentication; no long-lived npm token or `NPM_TOKEN` secret is required. npm
117
+ requires CLI 11.5.1 or newer and Node 22.14 or newer. Complete the configuration's
118
+ first successful CI publication within two days, or recreate the expired connection.
119
+ Public OIDC publications from this public repository receive provenance automatically.
120
+
121
+ For subsequent releases:
122
+
123
+ 1. Update the SDK version and lockfile together, for example with
124
+ `npm version 0.1.0-beta.2 --no-git-tag-version`, and commit the tested changes.
125
+ 2. Tag that commit `plugin-sdk-v0.1.0-beta.2` and push only that tag when ready to publish.
126
+ The workflow rejects a tag that does not exactly match `package.json`.
127
+ 3. The workflow validates the tarball first, then publishes prereleases to `next`
128
+ and stable versions to `latest`. Existing app workflows exclude SDK tags.
129
+
130
+ Run the **Plugin SDK** workflow manually to validate a branch or tag; this does not
131
+ publish, even when a tag is selected. Publishing requires a push of a matching
132
+ `plugin-sdk-v*` tag. Branch pushes and pull requests do not start this workflow.
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "flare-plugin-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-beta.2",
4
+ "description": "Types, tooling, templates and authoring references for Flare plugins",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "homepage": "https://github.com/DimensionDev/Flare#plugins-beta",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/DimensionDev/Flare.git",
10
+ "directory": "feature/plugin/sdk"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/DimensionDev/Flare/issues"
14
+ },
15
+ "bin": {
16
+ "flare-plugin-pack": "skill/flare-plugin/pack.mjs"
17
+ },
18
+ "types": "skill/flare-plugin/flare-plugin.d.ts",
19
+ "files": [
20
+ "skill/flare-plugin/"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org/"
28
+ },
29
+ "scripts": {
30
+ "test": "node --test scripts/*.test.mjs"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "6.0.3"
34
+ }
35
+ }
@@ -0,0 +1,334 @@
1
+ # Flare Social Plugin API v1
2
+
3
+ This directory is the authoring contract for Social Plugin API v1. Prefer
4
+ [GitHub distribution](references/publishing.md); local `.fpp` installation is
5
+ also supported. Start with the [authoring guide](README.md) or [agent skill](SKILL.md).
6
+ The Host implementation remains authoritative when this document and code disagree.
7
+
8
+ ## Package
9
+
10
+ An API v1 package is a ZIP with a `.fpp` extension and exactly these files:
11
+
12
+ ```text
13
+ manifest.json
14
+ plugin.js
15
+ assets/icon.png
16
+ locales/<bcp47>.json # optional
17
+ ```
18
+
19
+ The package must contain exactly one `assets/icon.<extension>`: `png`, `jpg`,
20
+ `jpeg`, `webp`, `gif`, or `ico`. Its extension must match its encoded contents.
21
+ The icon is kept unchanged, limited to 2 MiB and 1024×1024 pixels. SVG is not
22
+ supported. PNG remains the default in the templates.
23
+
24
+ `plugin.js` is one self-contained classic script. Static modules, dynamic
25
+ imports, remote code, filesystem access, and native access are unavailable.
26
+ Run `node pack.mjs <directory> [output.fpp]` to produce a deterministic package
27
+ using only Node's standard library.
28
+
29
+ ## GitHub installation and updates
30
+
31
+ Flare can install from a public `https://github.com/<owner>/<repository>` URL
32
+ (an optional `.git` suffix is accepted). Each repository contains one plugin at
33
+ its root: `manifest.json`, the self-contained, already-built `plugin.js`,
34
+ one supported `assets/icon.<extension>`, and optional `locales/*.json`. Other development files are
35
+ ignored. Flare does not run package managers, build scripts, or repository code
36
+ outside the existing plugin sandbox.
37
+
38
+ Publish a stable SemVer tag such as `1.2.3` or `v1.2.3`; removing the optional
39
+ lowercase `v` must exactly match `manifest.version`. Pre-release and arbitrary
40
+ tags are excluded from the version picker and update checks. A GitHub Release
41
+ or uploaded `.fpp` asset is not required.
42
+
43
+ The Host lists tags, compares semantic versions, resolves the selected commit,
44
+ and downloads its source ZIP by commit SHA. It imports the plugin files into a
45
+ canonical `.fpp` and applies the same package, registration, compatibility, and
46
+ permission checks as local installation. The repository archive is limited to
47
+ 20 MiB compressed, 50 MiB uncompressed, and 4,096 entries; existing per-file and
48
+ plugin package limits still apply. Traversal paths, symlinks, duplicate paths,
49
+ and multiple archive roots are rejected, including in ignored files.
50
+
51
+ GitHub installation records the repository ID, canonical owner/name, tag, and
52
+ commit SHA. Updates must retain that repository ID, plugin ID, and platform ID.
53
+ Checks only offer updates; downloading and confirming the existing install
54
+ review remain separate user actions, and changes take effect immediately.
55
+ New permissions, source changes, and a tag pointing to a different commit are
56
+ shown in the review. Cancellation or failure leaves the installed plugin intact.
57
+
58
+ Opening plugin management checks for updates using a five-minute metadata cache;
59
+ manual checks bypass it. Checks include all tag pages up to 1,000 tags and report
60
+ an error rather than claim a result from an incomplete list. GitHub requests use
61
+ the global proxy policy, without account credentials, and respect rate-limit retry
62
+ headers. Private repositories,
63
+ branch tracking, client-side builds, and automatic installation are unsupported.
64
+
65
+ ## Registration
66
+
67
+ The script calls `definePlugin` once. It registers named services; it does not
68
+ implement a universal dispatcher:
69
+
70
+ ```js
71
+ definePlugin({
72
+ detector: { async detect(request, context) {} },
73
+ login: {
74
+ oauth: {
75
+ async begin(request, context) {},
76
+ async resume(request, context) {},
77
+ },
78
+ },
79
+ capabilities: {
80
+ timeline: { async page(request, context) {} },
81
+ post: {
82
+ async detail(request, context) {},
83
+ async mutate(request, context) {},
84
+ },
85
+ },
86
+ });
87
+ ```
88
+
89
+ The manifest is the static upper bound. Every declared known operation must
90
+ have the corresponding function, or installation fails. Login returns the
91
+ subset supported by that account and instance. Unknown capability IDs remain
92
+ visible as compatibility warnings but are ignored by this Host.
93
+
94
+ Current method paths are:
95
+
96
+ | Capability | Service operations |
97
+ | --- | --- |
98
+ | `flare.datasource.timeline/v1` | `timeline.page` |
99
+ | `flare.datasource.search/v1` | `search.posts`, `profiles`, `discoverPosts`, `discoverProfiles`, `discoverHashtags` |
100
+ | `flare.datasource.profile/v1` | `profile.byId`, `byHandle`, `timeline`, `following`, `followers` |
101
+ | `flare.datasource.post/v1` | `post.detail`, `context`, `delete`, `mutate` |
102
+ | `flare.datasource.relation/v1` | `relation.state`, `mutate` |
103
+ | `flare.datasource.compose/v1` | `compose.publish` |
104
+ | `flare.datasource.notification/v1` | `notification.page`, `badge` |
105
+ | `flare.datasource.list/v1` | `list.page`, `detail`, `create`, `update`, `delete`, `timeline`, `members`, `memberships`, `addMember`, `removeMember` |
106
+ | `flare.datasource.direct-message/v1` | `directMessage.rooms`, `room`, `messages`, `send`, `delete`, `leave`, `create`, `badge`, `canSend` |
107
+ | `flare.datasource.article/v1` | `article.detail`, `comments` |
108
+ | `flare.datasource.gallery/v1` | `gallery.detail`, `comments`, `recommendations` |
109
+ | `flare.datasource.tab-catalog/v1` | `tabCatalog.page` |
110
+
111
+ The [capability matrix](references/capabilities.md) distinguishes required and
112
+ optional operations and explains UI dependencies. `PluginAbiV1.kt` defines the
113
+ Host contract. Use [flare-plugin.d.ts](flare-plugin.d.ts) for the complete v1
114
+ JavaScript types and [recipes](references/recipes.md) for implementation guidance.
115
+
116
+ Capabilities that map to a full Host interface must declare its required
117
+ operation set; installation rejects partial interfaces. Notification filters
118
+ (`all`, `mention`, `comment`, `like`) and relation actions (`follow`, `block`,
119
+ `mute`) are static arrays on their capability manifest declarations. They are
120
+ not asynchronous JavaScript methods because Flare needs them synchronously to
121
+ decide which controls to show.
122
+
123
+ ## Invocation and state
124
+
125
+ Native Apple targets use system JavaScriptCore, Android uses AndroidX JavaScriptEngine,
126
+ and Compose Desktop uses QuickJS. Await/return required asynchronous work: the Host
127
+ waits for started Host tasks before completing the invocation, but detached Promise
128
+ rejections do not automatically fail it. Returned/awaited errors retain normal typed
129
+ or fatal handling. Engine availability, version differences, cancellation, and resource
130
+ limits are described in [Runtime and HTTP](references/runtime-http.md).
131
+
132
+ Every method receives immutable request and context values. JavaScript globals
133
+ are disposable cache only. Plugins have no lifecycle or migration hooks.
134
+
135
+ Runtime scopes are detector attempt, login flow, account, and guest origin.
136
+ Calls in one Runtime serialize. A fatal JavaScript error, timeout, cancellation,
137
+ out-of-memory condition, or invalid Wire result retires that Runtime.
138
+
139
+ All values crossing the boundary are JSON Wire v1 DTOs. IDs use
140
+ `{ "id": "...", "host": "..." }`; page responses use `items`,
141
+ `olderCursor`, `newerCursor`, and `endReached`. Timestamps are ISO-8601 strings.
142
+ Rich text is `{ "format": "plain|html", "value": "..." }`; Flare sanitizes
143
+ HTML before rendering.
144
+
145
+ `PostV1` requires `key`, `createdAt`, and `content`. Its `author` and `visibility`
146
+ fields default to null and accept explicit null. A provided author must satisfy
147
+ the complete `ProfileV1` contract. The Host does not synthesize an author or infer
148
+ public visibility. These rules also apply to nested reposts and `updatedPost`
149
+ results; an updated post is a complete snapshot, so omitted fields do not retain
150
+ their previous values.
151
+
152
+ HTML resource and link URLs must use HTTPS without URL credentials. Relative
153
+ URLs resolve against the selected instance. Local files, `content:`, `data:`,
154
+ `javascript:`, and other schemes are rejected before rendering.
155
+
156
+ ## Post detail navigation
157
+
158
+ Each `PostV1` may set `detailType` to `"post"`, `"article"`, `"gallery"`, or `"link"`.
159
+ Omitting it defaults to `"post"`.
160
+ For example, a timeline may return an article preview with `detailType: "article"`
161
+ alongside regular posts and gallery previews. The selected detail receives that
162
+ post's `key` and uses the same account or guest origin. Reposted content keeps
163
+ its own `detailType`.
164
+
165
+ For internal details, the target capability must be available for that account
166
+ (or declared for guest access), including its required operations: `post.detail`; `article.detail` and
167
+ `comments`; or `gallery.detail`, `comments`, and `recommendations`. Otherwise the
168
+ post click does nothing; it does not fall back to another detail type. In media
169
+ grids, article and gallery previews use the same detail click target.
170
+
171
+ For `"link"`, the existing `url` field is required and must contain an absolute
172
+ HTTPS URL without URL credentials. The Host opens it through the existing browser
173
+ flow, following the user's browser settings and bypassing internal detail-link
174
+ matching. No post/article/gallery detail capability is required. Post and grid
175
+ cover clicks use this target; author and action buttons keep their own behavior.
176
+ For example (other required post fields omitted):
177
+
178
+ ```js
179
+ { detailType: "link", url: "https://example.com/article/123" }
180
+ ```
181
+
182
+ The URL remains optional for the three internal detail types. Missing or invalid
183
+ link URLs, and unknown or null `detailType` values, are invalid Wire results.
184
+
185
+ ## Host API
186
+
187
+ The frozen `flare` object exposes:
188
+
189
+ - `flare.http.request`: JSON, text, form, and streaming multipart HTTP;
190
+ - `flare.credential.read/replace`: the current account credential only;
191
+ - `flare.crypto.randomHex/uuid/sha256`;
192
+ - `flare.locale.current`;
193
+ - `flare.error`: marks a typed error safe to cross the ABI.
194
+
195
+ Authentication is structured:
196
+
197
+ ```js
198
+ await flare.http.request({
199
+ url: context.origin + "/api/v1/account",
200
+ authorization: { type: "bearer", token },
201
+ });
202
+ ```
203
+
204
+ Do not set raw `Authorization`, `Cookie`, `Host`, or `Content-Length` headers.
205
+ Compose bytes never enter JavaScript. Forward the request's asset handle:
206
+
207
+ ```js
208
+ await flare.http.request({
209
+ method: "POST",
210
+ url: context.origin + "/api/v1/media",
211
+ body: {
212
+ type: "multipart",
213
+ parts: [{ type: "asset", name: "file", handle: asset.handle }],
214
+ },
215
+ });
216
+ ```
217
+
218
+ Detector calls may access only the candidate origin. Login calls may also use
219
+ exact HTTPS origins listed in `permissions.authOrigins`. Account and guest
220
+ calls use the verified account origin. Redirects stay on the current origin.
221
+
222
+ Automatic media loading is a separate permission, disabled by default. A plugin
223
+ that returns avatars, images, audio, video, covers, or HTML images must declare
224
+ `"permissions": { "mediaNetwork": true }`. Installation review explicitly grants
225
+ media requests to any website, including CDNs and federated servers; these URLs
226
+ can transmit the plugin account's data. Adding this permission during an update
227
+ requires confirmation. It does not expand `flare.http.request` origins or permit
228
+ local-file resources. Text-only plugins do not need it. Cached profiles, posts,
229
+ article history, and direct messages retain their plugin ownership and recheck
230
+ the running package's grant when restored, including translated and nested media.
231
+ Permission removal and uninstall take effect immediately, like the
232
+ package change itself; absent plugins cannot restore media.
233
+ Development caches predating ownership metadata are rebuilt on upgrade.
234
+
235
+ Flare owns HTTP routing: detector, guest, and new-login requests follow the
236
+ global proxy policy; account requests and relogin requests follow that account's
237
+ policy, including OAuth resumed after restart. Plugins cannot select a proxy.
238
+ Proxy failures never fall back to direct connections, and changing a route may
239
+ cancel its in-flight requests, including uploads.
240
+
241
+ ## Login
242
+
243
+ For OAuth, Flare owns `flowId`, state, callback validation, persistence,
244
+ expiry, replay protection, and relogin identity validation. `begin` returns:
245
+
246
+ ```js
247
+ {
248
+ type: "externalBrowser",
249
+ url: authorizationUrl,
250
+ pendingPayload: { /* bounded opaque JSON */ },
251
+ }
252
+ ```
253
+
254
+ `resume` receives validated callback parameters and returns:
255
+
256
+ ```js
257
+ {
258
+ type: "success",
259
+ value: {
260
+ accountId,
261
+ origin: context.origin,
262
+ credential: { accessToken },
263
+ profile,
264
+ capabilities: { "flare.datasource.timeline/v1": ["page"] },
265
+ composeConfig,
266
+ },
267
+ }
268
+ ```
269
+
270
+ At least one displayable capability must be negotiated. `accountId` must equal
271
+ `profile.key.id`, and `profile.key.host` must equal the selected origin's host.
272
+ The Host retains this login profile as the current account's identity for account
273
+ menus, compose, notifications, discovery/search, and direct-message selection when
274
+ a plugin does not provide profile lookup. This fallback never enables arbitrary
275
+ profile lookup or profile timelines. Cached login media follows the current grant. Login cannot replace an existing account owned
276
+ by another platform with the same account ID and host.
277
+
278
+ ## Errors
279
+
280
+ Only errors created with `flare.error` are recoverable plugin errors:
281
+
282
+ ```js
283
+ throw flare.error({
284
+ code: "RateLimited",
285
+ message: { key: "error.rateLimited", fallback: "Try again later" },
286
+ retryAfterSeconds: 30,
287
+ });
288
+ ```
289
+
290
+ Codes are `AuthenticationRequired`, `NotFound`, `Validation`, `RateLimited`,
291
+ `Network`, `Remote`, `Unsupported`, `InvalidResponse`, and `Cancelled`.
292
+ Unmarked exceptions are fatal and retire the Runtime generation.
293
+
294
+ ## Manifest text, icon, and Deep Links
295
+
296
+ Plugin-authored manifest text may be either a final string or:
297
+
298
+ ```json
299
+ { "key": "timeline.home", "fallback": "Home" }
300
+ ```
301
+
302
+ Localized keys must exist in the default locale catalog. Resolution uses exact
303
+ BCP-47 tag, language-script, language, default locale, then fallback. The only
304
+ formatting feature in v1 is named `{argument}` substitution.
305
+
306
+ The packaged image is the platform icon on every target and the icon for default
307
+ timeline tabs. The first automatically added default tab uses `platform.name`;
308
+ other defaults and the manual-add list retain timeline titles. Other manifest
309
+ icons use Host semantic enum names. See the
310
+ [tab presentation rules](references/manifest.md#default-and-manually-added-tabs).
311
+ Deep Links, when needed, are manifest-only structured rules; JavaScript cannot
312
+ resolve them.
313
+
314
+ For full field defaults and validation, read the [manifest reference](references/manifest.md).
315
+ All five authentication interactions are covered in [authentication](references/authentication.md).
316
+
317
+ ## Lifecycle and trust
318
+
319
+ Install, update, and uninstall take effect immediately. Installed plugins are always enabled. Local packages are always unverified. Repository trust is
320
+ Host-owned; a manifest cannot claim official status or broader runtime access.
321
+
322
+ Uninstall removes the plugin's accounts, credentials, account-scoped caches,
323
+ selections/tabs, and draft targets from this device, together with local source
324
+ and package files. Shared drafts and attachments remain while another account
325
+ uses them. Updates preserve accounts. Loading failures or restoring accounts
326
+ without their plugin leave accounts unavailable; they do not delete them.
327
+ Login, publishing, and uploads can delay or block replacement/removal. Closed
328
+ sessions cannot recreate accounts through a late callback. Account ownership and
329
+ the persisted removal intent are checked before any account data is changed.
330
+ Once removal starts, an account-cleanup or final-index-write failure keeps the
331
+ plugin stopped, including after restart, and its management entry remains
332
+ available to retry deletion. Updates and installation cannot reactivate a plugin
333
+ with incomplete removal. Unused package-file cleanup is repeatable through
334
+ plugin management's cleanup action after deletion completes.