93 lines
5.2 KiB
Markdown
93 lines
5.2 KiB
Markdown
# The Store's source
|
|
|
|
Every item in the wayBetter Store is one folder here. A pull request that adds a folder adds an
|
|
item; nothing else has to be written by hand. The catalog, the item's page on
|
|
waybetter.fyi/store/, its card, search entry, link preview, structured data and sitemap entry are
|
|
all generated from these folders.
|
|
|
|
```
|
|
store/
|
|
featured.json the storefront's "Featured" shelf: ["x/theme/nord", …]
|
|
x/ one folder per app id ("x" = wayBetter for X)
|
|
plugins/<id>/
|
|
<name>.plugin.js the plugin, exactly as it installs
|
|
item.json { "category": "tools", "tags": ["notes", "profiles"] }
|
|
preview.svg | preview.png optional
|
|
areas/<id>/
|
|
item.json an area plugin's page: { "category", "tags", "license", "credits", media }
|
|
themes/<id>/
|
|
theme.json the theme, as Appearance → Export writes it
|
|
item.json { "version": "1.0.0" }
|
|
preview.svg | preview.png optional
|
|
strings/<id>/
|
|
<name>.strings.json a string pack (waybetter/docs/STRING-PACKS.md): name, authors, version,
|
|
description and language are the pack's own; every string must fit
|
|
the app's inventory (node scripts/check-store.mjs checks it)
|
|
item.json { "category": "language", "tags", "license", "credits" }
|
|
```
|
|
|
|
The folder name is the item's **id**: `a-z`, `0-9` and `-`, at most 40 characters, unique among
|
|
the app's plugins (or themes). It is also the item's address: `/store/plugins/<id>/`.
|
|
|
|
## What goes where
|
|
|
|
Everything the app shows on its install screen comes from the item's own file, so the Store and
|
|
the install screen can never disagree. `item.json` holds only what the file cannot say.
|
|
|
|
| | Plugin | Theme |
|
|
| --- | --- | --- |
|
|
| Name, author, description | the header: `@name`, `@author`, `@description` | `name`, `author`, `description` in `theme.json` |
|
|
| Version | the header: `@version` | `"version"` in `item.json` |
|
|
| Permissions | the header: `@permissions` | none (a theme is data) |
|
|
| Category | `"category"` in `item.json`: `reading`, `writing`, `filters`, `focus` or `tools` | `dark` or `light`, from the theme's `mode` (or `"category"`) |
|
|
| Tags | `"tags"` in `item.json`: up to 12 search words | the same (default: the category) |
|
|
| Preview | `preview.svg` or `preview.png`, else a card drawn from the name and category | the same, else a miniature drawn from the theme |
|
|
|
|
Rules the checks enforce:
|
|
|
|
- **Description**: at least 50 characters that say what it does. The app keeps the first 300 of a
|
|
plugin's; search engines show the first 160 or fewer, cut at a sentence.
|
|
- **Plugins** start with a `/** … */` header that has `@name`, `@version`, `@description`,
|
|
`@author` and a `@permissions` line naming `network` or `network:none`. `network:none` is refused
|
|
when the code calls `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource` or `sendBeacon`. The
|
|
gated API (`api.storage`, `api.ui`, `api.toast`, `api.copyText`, `api.account`, `api.users`,
|
|
`api.commands`) must be declared. No `eval`, `new Function`, `import()` or added `<script>`
|
|
elements: the code that is read and signed is the code that runs. At most 2 MiB, UTF-8 text.
|
|
- **Themes** are a JSON object with a `name` that the app's sanitizer accepts, at most 2 MiB. A
|
|
theme's optional `css` may not `@import` or load files from another server.
|
|
- **Previews** are 16:10 (640×400 is the size the Store shows), at most 300 KB. An SVG may not
|
|
contain scripts, event handlers, `foreignObject` or links to other servers.
|
|
|
|
## Area plugins
|
|
|
|
Compose+, Reader+, Profile+, Chat+, Media+, Search+, Notifications+ and Navigation+ are built into
|
|
wayBetter for X (docs/AREA-PLUGINS.md), so they are not catalog items and have no Install button.
|
|
Each has a page on the site from `store/x/areas/<id>/item.json` (category, tags, `license`,
|
|
`credits`, and the same optional cover, screenshots and video as an item). Its name, description
|
|
and feature list come from the app's feature table and words. The bundled plugin files those
|
|
features replaced stay in the app for the one-time move of an existing install and have no Store
|
|
folder.
|
|
|
|
`license` (1 to 60 characters, e.g. an SPDX id) and `credits` (up to 600) are optional on every
|
|
item and go into the catalog as they are.
|
|
|
|
## First-party items
|
|
|
|
The plugins the app ships and its built-in themes are in the Store too, but their files stay where
|
|
the app builds them, so the two copies cannot drift:
|
|
|
|
- `"bundled": "<file>.plugin.js"` takes the plugin from `apps/desktop/src-tauri/bundled-plugins/`;
|
|
`"file"` is the name it is installed under (`bt-<file>`), so a bundled copy counts as installed.
|
|
- `"builtin": true` exports the built-in theme whose id is the folder name, the way Appearance →
|
|
Export does.
|
|
|
|
## Commands
|
|
|
|
```sh
|
|
bun run check:store # check the folders and the published catalog (no key needed)
|
|
node scripts/build-store.mjs # maintainers: rebuild apps/web/public/store/ from the folders
|
|
node scripts/sign-store.mjs # maintainers: sign it, on the machine that holds the Store key
|
|
```
|
|
|
|
How a contribution becomes a Store item, step by step:
|
|
[docs/STORE.md](../docs/STORE.md#add-your-theme-or-plugin-with-a-pull-request).
|