# 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// .plugin.js the plugin, exactly as it installs item.json { "category": "tools", "tags": ["notes", "profiles"] } preview.svg | preview.png optional areas// item.json an area plugin's page: { "category", "tags", "license", "credits", media } themes// theme.json the theme, as Appearance → Export writes it item.json { "version": "1.0.0" } preview.svg | preview.png optional strings// .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//`. ## 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 `