Um tema
Cores, fontes, cantos, densidade, layout, fundo, efeitos. Três cores bastam; o resto é derivado e o texto continua legível.
Ou monte no app: Aparência → Criador de temas → Exportar. O THEMES.md lista tudo o que um tema pode definir.
(FOSS) Um pull request é uma submissão
Um tema é um arquivo JSON. Um plugin é um arquivo JavaScript. Crie, teste no app e depois adicione à Loja: uma pasta em um pull request, ou o arquivo em uma issue se você não usa git. Os dois caminhos pedem uma conta gratuita em git.crt.fyi, e mais nada.
Comece de um arquivo que funciona. O gerador escreve um para você; a seção Criar do próprio app (wayBetter → Criar) tem exemplos e os valores do tema ao vivo.
Cores, fontes, cantos, densidade, layout, fundo, efeitos. Três cores bastam; o resto é derivado e o texto continua legível.
Ou monte no app: Aparência → Criador de temas → Exportar. O THEMES.md lista tudo o que um tema pode definir.
Uma classe com start() e stop(), e um cabeçalho que diz quem fez e o que ela usa.
Adicione --ts para um projeto em TypeScript. O PLUGINS.md traz a API, as permissões e o recarregamento ao vivo.
/**
* @name Hide Trends
* @description Hides the trends box in the right column, so the timeline is the only thing moving.
* @version 1.0.0
* @author you
* @permissions network:none
*/
module.exports = class HideTrends {
start() { wayBetter.api.injectCSS('hide-trends', '[data-testid="sidebarColumn"] section { display: none }'); }
stop() { wayBetter.api.clearCSS('hide-trends'); }
};
A Loja lê o nome, o autor, a versão, a descrição e as permissões de um plugin nesse cabeçalho, e o nome, o autor e a descrição de um tema no JSON dele. A tela de instalação mostra os mesmos valores, então os dois nunca divergem.
A Loja é opcional. Um arquivo que funciona já pode ser compartilhado.
custom.css salvo recarrega em todas as abas de uma vez.waybetter://install para uma URL https em waybetter.fyi, cdn1.crt.fyi ou um arquivo bruto em git.crt.fyi. Ele nunca é instalado sem a tela de instalação.Todo item da Loja é uma pasta dentro de store/<app>/ no repositório. O nome da pasta é o id do item e o endereço dele aqui: /store/plugins/<id>/.
store/x/plugins/hide-trends/
hide-trends.plugin.js the plugin, exactly as it installs
item.json { "category": "focus", "tags": ["trends", "sidebar"] }
preview.svg optional, 640×400 (.svg or .png); drawn for you without one
store/x/themes/midnight-mint/
theme.json the theme, as Appearance → Export writes it
item.json { "version": "1.0.0" }
preview.svg optional; drawn from the theme without one
categoryreading, writing, filters, focus ou tools. Temas: dark ou light, tirado do mode do tema quando você não informa.tagsversionEssa é a submissão inteira. A página, o cartão, a entrada de busca, a prévia do link, os dados estruturados e a entrada do sitemap são gerados a partir do catálogo assinado quando o site é construído. O store/README.md traz todas as regras.
Não há CI hospedada. As verificações são comandos que você roda antes de enviar e que um mantenedor roda de novo antes do merge.
git submodule update --init
bun install --frozen-lockfile
bun run check:store # your folder, against the Store's rules
bun run test # everything else, including the site's SEO test
a-z 0-9 -, até 40), name.plugin.js, sem duplicatas em um app@name, @version, uma descrição e uma linha @permissions que informe network ou network:nonenetwork:none é recusado quando o código chama fetch, XMLHttpRequest, WebSocket, EventSource ou sendBeacon; chamadas à API restrita precisam ser declaradaseval, new Function ou import(); sem scripts ou arquivos externos em uma prévia; sem @import ou URLs externas no CSS de um temaDois caminhos. Os dois precisam de uma conta gratuita no git.crt.fyi: qualquer pessoa pode se cadastrar, e um mantenedor aprova cada conta nova à mão.
A chave de assinatura da Loja fica offline de propósito, então fazer merge não é publicar. Depois do merge, um mantenedor lê o código, roda waybetter store build e waybetter store sign na máquina que guarda a chave e envia. O site é reconstruído, seu item ganha a própria página, e todo app o enxerga na próxima vez que ler o catálogo assinado.
Tudo o que está no catálogo assinado é garantido por essa chave, e é por isso que uma pessoa lê cada plugin antes de assiná-lo.
Um arquivo para todos os apps wayBetter. Você nunca o edita à mão: ele é gerado a partir das pastas.
{ "format": 2, "name": "wayBetter Store", "generatedAt": "2026-09-30T…Z", "items": [
{ "apps": ["x"], "kind": "plugin", "id": "thread-reader", "name": "Thread Reader",
"author": "wayBetter", "version": "1.2.0", "description": "…",
"category": "reading", "tags": ["threads"],
"url": "https://waybetter.fyi/store/plugins/bt-thread-reader.plugin.js",
"file": "bt-thread-reader.plugin.js", "sha256": "<64 hex>", "size": 10961,
"preview": "https://waybetter.fyi/store/previews/plugin-thread-reader.svg",
"permissions": ["ui", "clipboard", "commands", "network:none"] }
] }
apps. Um app verifica o arquivo inteiro e fica só com os próprios itens.Não são itens da Loja: os 51 idiomas vêm embutidos no app e são carregados sob demanda.
apps/desktop/locales/<tag>.json, por pull request ou por uma issue com o arquivo alterado em anexo.bun run i18n:check: chaves, marcadores, formas de plural, sem marcação. Ele precisa imprimir 0 error(s).