wayBetter for X

This commit is contained in:
CRTkafa 2026-10-01 06:01:13 +07:00
commit 2f1e35ba20
720 changed files with 147465 additions and 0 deletions

93
store/README.md Normal file
View file

@ -0,0 +1,93 @@
# 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).