From d8875a3ec05e82c7f31a4b0bf764e01eff09be7e Mon Sep 17 00:00:00 2001 From: diogoshk3 Date: Mon, 20 Jul 2026 18:50:41 +0100 Subject: [PATCH] chore: remove development tool attribution --- CLAUDE.md => CONTRIBUTING.md | 4 +- docs/PLAN-CONFIG-COMUNIDADE.md | 14 ++- docs/PLAN-GDPR.md | 32 ++++--- docs/PLAN-PAINEL.md | 63 ++++++++----- docs/PLAN-SETTINGS-UI.md | 44 +++++---- docs/PLAN-SITE.md | 27 ++++-- docs/PLAN.md | 20 +++- docs/RESEARCH-DISCORD-API.md | 127 +++++++++++++------------- docs/RESEARCH-FEATURES-MODERACAO.md | 27 ++++-- docs/RESEARCH-TOP-SERVERS-FEATURES.md | 47 ++++++---- plans/009-eslint-prettier-ci.md | 18 ++-- plans/README.md | 28 +++--- 12 files changed, 281 insertions(+), 170 deletions(-) rename CLAUDE.md => CONTRIBUTING.md (95%) diff --git a/CLAUDE.md b/CONTRIBUTING.md similarity index 95% rename from CLAUDE.md rename to CONTRIBUTING.md index 50e6bf9..709bb09 100644 --- a/CLAUDE.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# CLAUDE.md +# Contribuir para o Vozen Helper -Guia para agentes de IA a trabalhar no **Vozen Helper** (bot de moderação privado). +Regras e guia de desenvolvimento do **Vozen Helper** (bot de moderação privado). ## Comandos diff --git a/docs/PLAN-CONFIG-COMUNIDADE.md b/docs/PLAN-CONFIG-COMUNIDADE.md index 77c695b..5fead78 100644 --- a/docs/PLAN-CONFIG-COMUNIDADE.md +++ b/docs/PLAN-CONFIG-COMUNIDADE.md @@ -1,6 +1,6 @@ # Plano — Ativar as features de comunidade no Vozen Support -> Planeado com Fable 5 (2026-07-13). Execução: Opus. O bot e as features já estão +> Planeado em 2026-07-13. O bot e as features já estão > no VPS; isto é o plano de CONFIGURAÇÃO (canais, cargos, IDs) para as tornar vivas. ## Objetivo @@ -26,11 +26,13 @@ canal de painel de tickets, canal de transcripts, cargos de nível. ## Scope ### In + - Criar 4 canais + 1 canal de voz + 3 cargos de nível (via script one-shot com o token do bot). - Preencher `modConfig.community` com os IDs; build + deploy + restart no VPS. - Painéis publicados (`/ticket-panel`) e verificação de cada feature. ### Out + - Boas-vindas e aniversários (REMOVIDOS a pedido do Diogo — não recriar). - Self-roles `/rolepanel` (já funciona sem config; o Diogo publica painéis quando quiser). - Mudanças de lógica/features novas; XP de voz; rank cards em imagem. @@ -38,7 +40,9 @@ canal de painel de tickets, canal de transcripts, cargos de nível. ## Fases ### Fase 1 — Criar a infraestrutura no Discord (script one-shot) + Deliverable: canais e cargos criados, IDs impressos. + - [ ] Script `tools/setup-community.mjs` (usa DISCORD_TOKEN/GUILD_ID do .env; correr LOCALMENTE uma vez): - [ ] Texto `₊˚ʚ💡୧﹕sugestões` na categoria "Canais de Texto" — escrita bloqueada para @everyone (só o bot posta; membros usam /suggest) - [ ] Texto `₊˚ʚ⭐୧﹕destaques` (starboard) na mesma categoria — escrita bloqueada para @everyone @@ -50,7 +54,9 @@ Deliverable: canais e cargos criados, IDs impressos. - **Done**: script imprime o mapa nome→ID completo, sem erros. ### Fase 2 — Preencher a config + Deliverable: `src/config.ts` com o bloco `community` real. Dependências: Fase 1 (IDs). + - [ ] `suggestions.channelId` = #sugestões - [ ] `memberCounter.channelId` = canal de voz 📊; `template: '📊 Membros: {count}'` - [ ] `leveling.announceChannelId` = #general-chat (servidor pequeno — canal dedicado seria deserto); `levelRoles: [{5,🥉},{10,🥈},{20,🥇}]`; `stackRoles: false` (substitui — mostra só o marco mais alto); `noXpChannelIds: [mod-helper-bot, bot-testing]` (anti-farm em canais de bot) @@ -60,14 +66,18 @@ Deliverable: `src/config.ts` com o bloco `community` real. Dependências: Fase 1 - **Done**: typecheck verde; IDs todos validados contra o output da Fase 1. ### Fase 3 — Deploy + ativação + Deliverable: features vivas no servidor. Dependências: Fase 2. + - [ ] `npm run build` + `npx vitest run` verdes localmente - [ ] tar do `src` → VPS, build no VPS, restart do bot (método habitual: matar o filho, supervisor sobe) - [ ] Publicar o painel de tickets: `/ticket-panel` no #suporte - **Done**: bot online (`Vozen Helper pronto` no log), contador de voz renomeado com o nº real. ### Fase 4 — Verificação feature a feature + Deliverable: prova de que cada uma funciona. + - [ ] `/suggest teste` → embed #1 em #sugestões, votos 👍/👎 respondem - [ ] Contador de voz mostra "📊 Membros: N" correto - [ ] Mensagem no #general-chat dá XP; `/rank` responde; (o level-up 5 só se testa com uso real) @@ -77,6 +87,7 @@ Deliverable: prova de que cada uma funciona. - **Done**: checklist toda verde; pedir ao Diogo para confirmar visualmente. ## Riscos + - **Permissões do script**: criar canais/cargos exige `Manage Channels`/`Manage Roles` — o bot tem ambas; se algum overwrite falhar (ex.: connect do canal de voz), criar na mesma e avisar, não abortar. - **Cargos de nível acima do bot**: o script cria-os automaticamente ABAIXO do cargo do bot — sem risco de hierarquia. - **Starboard em servidor pequeno**: threshold 3 pode banalizar os destaques; é config de 1 linha se o Diogo quiser subir para 4–5. @@ -84,6 +95,7 @@ Deliverable: prova de que cada uma funciona. - **Level-ups no #general-chat**: se vier a incomodar, muda-se `announceChannelId` para um canal dedicado (1 linha). ## MVP + Fim da Fase 3: tudo configurado e vivo. A Fase 4 é a prova. **Próxima ação concreta: escrever `tools/setup-community.mjs` (Fase 1) e corrê-lo uma vez para criar os canais/cargos e obter os IDs.** diff --git a/docs/PLAN-GDPR.md b/docs/PLAN-GDPR.md index 2ee973b..31820b6 100644 --- a/docs/PLAN-GDPR.md +++ b/docs/PLAN-GDPR.md @@ -1,6 +1,6 @@ # Plano — Conformidade GDPR do Vozen Helper (site + API + bot) -> Planeado com Fable 5 (2026-07-15). ✅ EXECUTADO (2026-07-15, Opus). Fases 1–4 feitas. +> Planeado em 2026-07-15. ✅ EXECUTADO em 2026-07-15. Fases 1–4 feitas. > Entregáveis: `docs/GDPR-INVENTARIO.md`; `site/privacidade.html` + link no gate (no ar); > `src/store/gdpr.ts` (purga/export/apagar) ligado ao arranque e ao member-leave; > comando `/privacidade dados|apagar`; `deploy/vozen-panel-logrotate.conf`. @@ -23,6 +23,7 @@ de conformidade. Tu és o responsável pelo tratamento (controller); Discord, Gi ## Scope ### In + - Inventário de dados pessoais (registo de tratamento simplificado, art. 30.º). - Página de **Política de Privacidade** no site (pt-PT) + link visível no gate. - Retenção: purga automática de dados antigos na BD e rotação de logs no VPS. @@ -32,6 +33,7 @@ de conformidade. Tu és o responsável pelo tratamento (controller); Discord, Gi sem banner de consentimento, mas com menção na política). ### Out + - Banner de cookies (não há trackers, analytics nem cookies não-essenciais — não é preciso). - DPO, DPIA formal, representante na UE (escala não o exige). - Contratos DPA formais com GitHub/Cloudflare/Discord (usam os termos standard deles; só divulgar). @@ -41,42 +43,50 @@ de conformidade. Tu és o responsável pelo tratamento (controller); Discord, Gi ## Fases ### Fase 1 — Inventário de dados (registo de tratamento) + Deliverable: `docs/GDPR-INVENTARIO.md` — tabela: dado → onde vive (tabela BD / log / localStorage / cookie) → finalidade → base legal → retenção proposta. Dep.: nenhuma. + - [ ] Mapear as ~20 tabelas da BD (`src/store/db.ts`) + `api.log` + logs do túnel + - localStorage/cookie do painel. + localStorage/cookie do painel. - [ ] Classificar base legal por dado: **interesse legítimo** (moderação: cases, notes, - infractions, quarantine, anti-raid), **consentimento por ato voluntário** (birthdays, - AFK, lembretes, votos, giveaways), **interesse legítimo** (stats/levels — a validar). + infractions, quarantine, anti-raid), **consentimento por ato voluntário** (birthdays, + AFK, lembretes, votos, giveaways), **interesse legítimo** (stats/levels — a validar). - [ ] Propor retenção por categoria (ex.: cases 2 anos, stats 1 ano, api.log 30 dias). - **Done:** cada dado pessoal identificado tem linha na tabela com os 5 campos preenchidos. ### Fase 2 — Política de privacidade no site + Deliverable: `site/privacidade.html` publicada + link no gate. Dep.: Fase 1 (a política descreve o que o inventário apurou — sem inventário, a política mente). + - [ ] Redigir em pt-PT claro: quem é o responsável, que dados, para quê, base legal, - retenção, com quem se partilha (Discord/GitHub/Cloudflare/VPS), direitos e como - exercê-los (contacto Discord), cookie `vh_session` + localStorage. + retenção, com quem se partilha (Discord/GitHub/Cloudflare/VPS), direitos e como + exercê-los (contacto Discord), cookie `vh_session` + localStorage. - [ ] Página estática com os tokens visuais do painel; link "Privacidade" no rodapé do gate. - [ ] Mencionar a política na descrição do bot / canal de regras do servidor (os membros - do servidor são os titulares — têm de conseguir encontrá-la). + do servidor são os titulares — têm de conseguir encontrá-la). - **Done:** URL pública abre a política; gate tem link visível; membros conseguem chegar lá. ### Fase 3 — Retenção e minimização + Deliverable: purga automática na BD + rotação de logs. Dep.: Fase 1 (os prazos vêm do inventário). + - [ ] Job diário no bot: apagar/anonimizar registos além da retenção (cases antigos → - manter contagem, apagar user_id? decidir na Fase 1; stats/levels de quem saiu do servidor). + manter contagem, apagar user_id? decidir na Fase 1; stats/levels de quem saiu do servidor). - [ ] Rotação do `api.log` no VPS (logrotate ou truncagem no supervisor, 30 dias). - [ ] Auditar o `api.log`: registar ações, não dados desnecessários. - [ ] Testes (vitest) da função de purga: TDD como o resto do projeto. - **Done:** inserir registo com timestamp antigo + correr purga → desaparece; log não cresce sem limite. ### Fase 4 — Direitos dos titulares + Deliverable: comandos Discord de exportação e apagamento. Dep.: Fases 1 e 3. + - [ ] `/privacidade dados` — DM com JSON de tudo o que a BD tem sobre o requerente. - [ ] `/privacidade apagar` — apaga dados voluntários (birthday, AFK, lembretes, votos, - XP…) com confirmação; **recusa fundamentada** para registos de moderação ativos - (interesse legítimo prevalece — art. 17.º/3) com resposta clara a dizê-lo. + XP…) com confirmação; **recusa fundamentada** para registos de moderação ativos + (interesse legítimo prevalece — art. 17.º/3) com resposta clara a dizê-lo. - [ ] Registar pedidos de apagamento (data + user) para prova de cumprimento. - [ ] Testes de ambos os comandos. - **Done:** conta de teste recebe o seu JSON completo; após apagar, `/privacidade dados` @@ -95,7 +105,7 @@ Deliverable: comandos Discord de exportação e apagamento. Dep.: Fases 1 e 3. ## Riscos -- **Isenção doméstica é ambígua:** um servidor Discord privado *pode* cair fora do GDPR +- **Isenção doméstica é ambígua:** um servidor Discord privado _pode_ cair fora do GDPR (uso pessoal, art. 2.º/2-c), mas a jurisprudência trata comunidades com membros como tratamento real. Assumimos que o GDPR se aplica — se não se aplicar, o trabalho fica a mais, nunca a menos. Custo baixo, risco eliminado. diff --git a/docs/PLAN-PAINEL.md b/docs/PLAN-PAINEL.md index 82585bd..3854bc2 100644 --- a/docs/PLAN-PAINEL.md +++ b/docs/PLAN-PAINEL.md @@ -1,6 +1,6 @@ # Plano — Painel web de controlo do Vozen Helper -> Planeado com Blueprint (2026-07-14). Execução: Opus, à parte. Painel atrás do login +> Planeado em 2026-07-14. Painel atrás do login > Discord já existente (site `vozen-helper-bot`, GitHub Pages) que controla A SÉRIO o bot > que corre na VPS. **Planeamento only — não se escreve código nesta fase.** @@ -12,6 +12,7 @@ redeploy**. Requer uma **API HTTP na VPS** ligada à SQLite do bot, com **HTTPS* a partir do origin `https://rexy40407.github.io` e **auth real** via Discord OAuth. **Assunções** (mudam a execução se erradas): + - O bot corre na VPS sob supervisor + cron `@reboot`, sem sudo (como o Vozen). A API segue o mesmo padrão (processo de utilizador, sem serviços de sistema). - Usa-se **Cloudflare Tunnel** (`cloudflared`, grátis, sem sudo) para dar um URL HTTPS @@ -24,6 +25,7 @@ a partir do origin `https://rexy40407.github.io` e **auth real** via Discord OAu ## Scope ### In + - API HTTP (TypeScript, mesma stack do bot) na VPS: sessão via Discord OAuth, leitura de casos/stats/config, escrita de settings e ações de caso. - **Camada de settings em runtime** no bot: tabela `settings` + resolver que sobrepõe @@ -33,6 +35,7 @@ a partir do origin `https://rexy40407.github.io` e **auth real** via Discord OAu - Deploy da API (supervisor + cron `@reboot`) e do túnel. ### Out + - Multi-utilizador / permissões por cargo (é só 1 conta dona). - Editar `modConfig` no código pelo painel (a config passa a ser overrides na BD, não edição de ficheiros + redeploy). @@ -43,19 +46,22 @@ a partir do origin `https://rexy40407.github.io` e **auth real** via Discord OAu ## Fases ### Fase 0 — Spike de alcance HTTPS (de-risk primeiro) ✅ FEITO (2026-07-14) + Deliverable: um endpoint trivial `GET /health` na VPS, servido por **HTTPS** através do Cloudflare Tunnel, chamável do browser a partir de `https://rexy40407.github.io`. Dependências: nenhuma. **É aqui que se descobre cedo o maior desconhecido.** + - [x] Instalar `cloudflared` como binário de utilizador na VPS (sem sudo) e criar um túnel - para um mini-servidor local (`GET /health` → `200 {ok:true}`), com CORS a permitir só o - origin `github.io`. + para um mini-servidor local (`GET /health` → `200 {ok:true}`), com CORS a permitir só o + origin `github.io`. - [x] Da internet, `curl` ao URL do túnel devolve 200 `{ok:true}` sobre HTTPS; com header - `Origin: github.io` responde `Access-Control-Allow-Origin: github.io`; origin não - autorizado → sem esse header (trancado). + `Origin: github.io` responde `Access-Control-Allow-Origin: github.io`; origin não + autorizado → sem esse header (trancado). - **Done:** ✅ provado ponta-a-ponta (`https://.trycloudflare.com/health` → `{ok:true}`, CORS ok). Spike desligado a seguir; bot intacto. **Notas operacionais (Fase 0) — importam para a Fase 5:** + - **Persistência:** processos arrancados numa sessão SSH **são mortos** ao fechar a sessão (só cron/`atd` sobrevivem — o bot corre por `@reboot cron`). ⇒ a API + túnel de produção têm de ir para **cron `@reboot`** (ou supervisor lançado por cron), como o bot. No spike @@ -69,65 +75,75 @@ Dependências: nenhuma. **É aqui que se descobre cedo o maior desconhecido.** ou colar o domínio quando existir. ### Fase 1 — API: esqueleto + autenticação real ✅ FEITO (2026-07-14) + Deliverable: serviço HTTP com sessão segura; só a conta permitida entra. Dep.: Fase 0. + - [x] Servidor HTTP (Fastify) no repo do bot (`src/api/`), processo separado, a abrir a - SQLite do bot em leitura (`openReadOnly`, `busy_timeout`). Ouve só em `127.0.0.1`. + SQLite do bot em leitura (`openReadOnly`, `busy_timeout`). Ouve só em `127.0.0.1`. - [x] `POST /api/session`: recebe o Bearer token do Discord, chama `/users/@me` **no - servidor** (`fetchDiscordUser`), confirma `id === PANEL_ALLOWED_USER_ID`, e emite cookie - de sessão **assinado + httpOnly + Secure + SameSite=None**. Guarda protege `/api/me`. + servidor** (`fetchDiscordUser`), confirma `id === PANEL_ALLOWED_USER_ID`, e emite cookie + de sessão **assinado + httpOnly + Secure + SameSite=None**. Guarda protege `/api/me`. - [x] CORS trancado ao `PANEL_ALLOWED_ORIGIN` (github.io) com `credentials`. - **Done:** ✅ 16 testes (inject) verdes: conta certa → 200+cookie; outra → 403; token inválido → 401; sem token → 400; Discord em baixo → 502; cookie forjado → 401. Suite completa 143/143, typecheck+build+lint limpos, boot local confirmado (`/health`, `/api/session`, `/api/me`). Ficheiros: `src/api/{config,discordAuth,db, - server,index}.ts`, `tests/api.test.ts`. Env novas em `.env.example` (`PANEL_*`). +server,index}.ts`, `tests/api.test.ts`. Env novas em `.env.example` (`PANEL_*`). **Falta (Fase 5):** correr na VPS por cron `@reboot` + túnel — ainda não deployado. ### Fase 2 — Leitura: dados reais no painel (MVP) ✅ CÓDIGO FEITO (2026-07-14) + Deliverable: painel autenticado que mostra dados reais da VPS. Dep.: Fase 1 + UI base. + - [x] `GET /api/cases` (casos recentes do guild, teto 200) e `GET /api/stats` - (mensagens/entradas/saídas + total de casos) — guardadas, SQL parametrizado, scoped ao - `GUILD_ID`. Helpers `getRecentCases`/`countCases` em `store/cases.ts`. + (mensagens/entradas/saídas + total de casos) — guardadas, SQL parametrizado, scoped ao + `GUILD_ID`. Helpers `getRecentCases`/`countCases` em `store/cases.ts`. - [x] Painel no site (`vozen-helper-bot`): o estado autorizado abre o painel (header + - logout, cards de stats, tabela de casos com tags por tipo). `POST /api/session` com o - token → cookie → `GET /api/stats` + `/api/cases`. **Degrada com elegância** se - `API_BASE` estiver vazio (banner "API ainda não ligada") — não parte o login atual. + logout, cards de stats, tabela de casos com tags por tipo). `POST /api/session` com o + token → cookie → `GET /api/stats` + `/api/cases`. **Degrada com elegância** se + `API_BASE` estiver vazio (banner "API ainda não ligada") — não parte o login atual. - **Done (código):** ✅ 22 testes API verdes (inclui cases/stats), suite 149/149, typecheck+lint limpos; painel verificado no browser (render de stats/casos + estado offline, sem erros de consola). **Falta para o MVP "ao vivo":** deploy da API na VPS + `API_BASE` apontado ao túnel (feito na Fase 5 / integração). ### Fase 3 — Bot: camada de settings em runtime ✅ FEITO (2026-07-14) + Deliverable: o bot passa a ler config sobreponível da BD. Dep.: antes da Fase 4. + - [x] Migração v6: tabela `settings (guild_id, key, value, updated_at)` (append-only). - [x] Resolver `store/flags.ts`: `isFeatureEnabled` (hot-path, cache TTL 4s) + - `getEffectiveFlags` (default do `modConfig` sob overrides da BD). Store `store/settings.ts`. + `getEffectiveFlags` (default do `modConfig` sob overrides da BD). Store `store/settings.ts`. - [x] **10 guards** ligados: anti-spam/scam/nuke, join gate, raid, leveling, starboard, - sugestões, tickets, bump reminder passam a ler pelo resolver (default = valor do modConfig). + sugestões, tickets, bump reminder passam a ler pelo resolver (default = valor do modConfig). - **Done:** ✅ escrever `flag.antispam=false` em `settings` desliga o anti-spam **sem redeploy** (o bot apanha via cache em ≤4s). Testado (5 testes settings/flags). Migração aplicada na VPS (user_version=6) e bot reiniciado (SIGKILL → supervisor respawn). ### Fase 4 — Escrita: o painel controla mesmo o bot ✅ FEITO (2026-07-14) + Deliverable: alterações no painel refletem-se no bot ao vivo. Dep.: Fases 2 + 3. + - [x] `GET /api/flags` (estado efetivo) + `PATCH /api/flags` — **allowlist** de chaves, - validação de booleano, **rate-limit** (120/min), **auditoria** no api.log. DB read-write - (`openApiDb`), só escreve `settings`. + validação de booleano, **rate-limit** (120/min), **auditoria** no api.log. DB read-write + (`openApiDb`), só escreve `settings`. - [x] Painel: secção **Definições** com toggles on/off por subsistema; PATCH otimista com - reversão + banner de erro. Auth por token no header. + reversão + banner de erro. Auth por token no header. - **Done:** ✅ provado E2E no sistema live — PATCH autenticado → 200 → linha gravada em `settings` → bot reflete. 167 testes verdes, typecheck+lint limpos. API deployada. **Nota de segurança:** só toggles booleanos allowlisted (sem edição livre de config) para conter o risco da superfície de escrita exposta. ### Fase 5 — Deploy permanente + persistência ✅ FEITO (2026-07-14, parcial) + Deliverable: API + túnel a correr 24/7 na VPS, hands-off. Dep.: Fase 4. + - [x] API a correr na VPS (`dist/api/index.js`, 127.0.0.1:8788) + túnel Cloudflare quick - tunnel (grátis, sem domínio — ngrok fixo passou a pago). + tunnel (grátis, sem domínio — ngrok fixo passou a pago). - [x] **Persistência hands-off:** cron `@reboot` corre `~/panel/panel-run.sh`, que arranca - a API + túnel e **republica o URL** (efémero) em `site/api-url.js` do repo do site via - **deploy key** (write). O site lê `window.API_BASE` desse ficheiro → segue sempre o URL - atual, mesmo mudando em reinícios. Auth por **token no header** (à prova de Brave). + a API + túnel e **republica o URL** (efémero) em `site/api-url.js` do repo do site via + **deploy key** (write). O site lê `window.API_BASE` desse ficheiro → segue sempre o URL + atual, mesmo mudando em reinícios. Auth por **token no header** (à prova de Brave). - **Done:** ✅ ciclo provado — reinício do túnel → novo URL → push automático → Pages redeploy → site aponta ao novo URL (`api-url.js` ao vivo confirmado). Sobrevive a reboots pelo cron. @@ -169,6 +185,7 @@ fontes Unbounded/Outfit/JetBrains Mono, raio 22px, vidro `rgba(22,25,41,.72)`. Além dos toggles, o painel escolhe **canais** por feature (settings `chan.*`/`xp.exclude`, mesma allowlist/validação/auditoria): + - **Canal de logs** (override único p/ todas as categorias), **sugestões**, **starboard**. - **Anúncios de nível**: canal específico OU "onde a pessoa falou" (`current`). - **Excluir do XP**: multi-seleção de canais (`xp.exclude`, CSV). diff --git a/docs/PLAN-SETTINGS-UI.md b/docs/PLAN-SETTINGS-UI.md index 1d03732..4c39ef3 100644 --- a/docs/PLAN-SETTINGS-UI.md +++ b/docs/PLAN-SETTINGS-UI.md @@ -1,11 +1,11 @@ # Plano — Redesenho visual das Definições do painel -> ✅ EXECUTADO (2026-07-14, Opus). Todas as fases feitas e no ar. Só `site/index.html` +> ✅ EXECUTADO em 2026-07-14. Todas as fases feitas e no ar. Só `site/index.html` > (front-end); zero backend. Verificado no browser (chips multi sem Ctrl, dropdown com > pesquisa+teclado, barra Guardar/Descartar, save mockado com 2 PATCHes, mobile sem > overflow, consola limpa). `cleanChannelName` com fallback a nomes só-emoji. -> Planeado com Fable 5 (2026-07-14). Execução: Opus. Só o front-end do painel +> Planeado em 2026-07-14. Só o front-end do painel > (`vozen-helper-bot/site/index.html`) — a API já dá tudo o que é preciso > (`/api/flags`, `/api/channels`, `/api/channel-settings`); **zero alterações de backend**. @@ -21,6 +21,7 @@ feedback de gravação e responsivo. ## Scope ### In + - Chips clicáveis no `xp.exclude` (multi) e dropdown custom com **pesquisa** nos restantes. - Limpeza dos nomes de canal decorados (`₊˚ʚ💎୧﹕joins` → `#joins`). - Agrupamento: **Moderação** e **Comunidade**, com o canal de cada feature junto ao toggle. @@ -32,6 +33,7 @@ feedback de gravação e responsivo. - Responsivo < 720px e acessibilidade (foco, teclado, ≥44px). ### Out + - Alterações à API/bot; novas settings; frameworks/bundlers; refazer login/stats/casos; tema claro; endpoint de gravação em lote no backend (o Guardar envia os PATCHes existentes um a um). @@ -39,46 +41,54 @@ feedback de gravação e responsivo. ## Fases ### Fase 1 — Chips multi-select + nomes limpos (a dor real) + Deliverable: `xp.exclude` com chips de canais clicáveis; nomes legíveis em todo o painel. Dep.: nenhuma. + - [ ] `cleanChannelName(name)`: remove decorativos unicode/emoji, devolve `nome` legível; - fallback ao nome original se ficar vazio. Aplicar em TODAS as UIs de canal. + fallback ao nome original se ficar vazio. Aplicar em TODAS as UIs de canal. - [ ] Substituir o `