1Contexto
O app participa é o palco; estes onze repositórios são o elenco. Antes de olhar cada
um, precisamos saber o que é “um módulo Decidim” — e por que o time escolheu criá-los.
1.1 Anatomia de um módulo Decidim
Um módulo (ou “engine”) é, por fora, uma gem Ruby comum; por dentro, é um mini-app Rails embutido no principal. O esqueleto é sempre o mesmo:
<nome>.gemspec— a identidade: nome, versão (Decidim::X::VERSION), dependências (decidim-core, etc.).lib/decidim/<nome>/engine.rb— a classeRails::Engine. É onde o módulo se pendura no Decidim:initializer,config.to_prepare, registro de componentes, ícones, view paths.app/— cells, controllers, forms, models, views, packs (SCSS/JS) do módulo.db/migrate/— migrations próprias, quando o módulo tem tabelas.
Um app consome o módulo com uma linha no Gemfile. Quando o módulo ainda não foi publicado em
RubyGems, usa-se um ponteiro de git para uma branch, e o Bundler resolve para um commit
exato, registrando-o no Gemfile.lock:
gem "decidim-bp_meetings",
git: "https://gitlab.com/lappis-unb/decidimbr/components-brasil-participativo/decidim-bp_meetings.git",
branch: "main"
# …o lock congela a resolução:
# remote: …/decidim-bp_meetings.git
# revision: 6fe033050ed6e128fb436ef16bd434d106b4acba
prepend: insere um módulo antes da classe original na cadeia de ancestrais — a sua implementação vence a do core sem editá-la. to_prepare: gancho que roda no boot e a cada reload em dev. Deface: reescreve HTML do core por seletor. content block: um bloco de página (ex.: a home) que o admin liga/desliga.
1.2 O recorte: 11 repositórios
Estes são os repositórios criados e efetivamente usados pelo participa (os únicos com ponteiro git
no Gemfile). Todos foram clonados na revisão fixada no lock:
| Repositório | Gemspec (nome da gem) | Revisão fixada |
|---|---|---|
| omniauth-govbr | omniauth-govbr | 5e9c9282 |
| participa-gem | decidim-apartment | 1eb5c0da |
| decidim-bp_templates | decidim-community_templates | 4784a016 |
| decidim-module-questionnaires | decidim-questionnaires | c778fdc8 |
| decidim-participatory_text | decidim-participatory_texts | d4111173 |
| decidim-bp_proposals | decidim-bp_proposals | 0447023f |
| decidim-bp_meetings | decidim-bp_meetings | 6fe03305 |
| decidim-bp_comments | decidim-bp_comments | c5be9a81 |
| decidim-categories | decidim-categories | 66919442 |
| decidim-government-spaces | decidim-government_spaces | cba43425 |
| decidim-extra_home_blocks | decidim-extra_home_blocks | b4a41ac4 |
Note que nome do repositório e nome da gem divergem em vários casos — bp_templates
publica decidim-community_templates; participa-gem publica
decidim-apartment. Ao procurar código, vá pelo gemspec.
2Intuição
2.1 Customizar sem fork — a ideia central
O jeito ingênuo de mudar o comportamento de uma dependência é forkar: copiar o repositório do Decidim e editar. O custo aparece depois — a cada release do Decidim, você reconcilia o seu fork com o upstream, para sempre.
O time escolheu o caminho oposto em quase todos os módulos: não forkar; estender em tempo de boot. Um
módulo pequeno, instalado por cima, insere suas próprias classes na frente das do core com
prepend. O código do core permanece intacto; só o comportamento observado muda.
Um exemplo concreto, tirado de decidim-bp_meetings. O requisito brasileiro é um toggle
positivo por etapa (“Usuário cidadão pode comentar”), mas o core só oferece um toggle negativo
global. O módulo resolve assim:
# decidim-bp_meetings/lib/decidim/bp_meetings/features/per_step_comments.rb
# …
# Candidato a upstream: parcial. O toggle positivo por etapa é generalizável,
# mas a remoção do global da UI é uma decisão BR que upstream provavelmente
# não aceitaria sem migração extensa.
def self.register_settings
Decidim.component_registry.find(:meetings)&.tap do |manifest|
manifest.settings(:step) do |settings|
settings.attribute :comments_enabled, type: :boolean, default: true
end
end
end
Cada feature vive em seu próprio arquivo, com um cabeçalho dizendo a origem do requisito e se aquilo é candidato a virar PR no upstream. Isso transforma “por que isto existe?” em algo legível no próprio código.
2.2 Três naturezas (e um fork)
Ao ler os onze repositórios com atenção, eles se separam em três naturezas — mais uma exceção:
Por que isso importa? Porque a durabilidade de cada peça é diferente. As customizações sobrevivem a
upgrades do Decidim com pouco trabalho. O fork é o que dói: community_templates não tem release
compatível com 0.32, e é exatamente por isso que o app mantém o diretório .disabled-for-0.32/.
3Panorama e linha do tempo
Os onze repositórios, com sua natureza, tamanho e janela de vida. A soma é ~281 commits.
| Repositório | Natureza | Commits | Janela | Diff em uma frase |
|---|---|---|---|---|
| decidim-module-questionnaires | customização | 71 | mai–set/2026 | Overrides do decidim-forms: numeração de seções/perguntas, i18n, assets. |
| participa-gem | infra | 56 | ago/2025–ago/2026 | Apartment para Decidim: schema por organização + elevator por host. |
| decidim-bp_templates | fork | 39 | set/2025–ago/2026 | Fork do community_templates, portado para Decidim 0.32.1. |
| decidim-participatory_text | componente | 28 | abr–ago/2026 | Componente de texto participativo com edição por parágrafo (Hotwire). |
| decidim-extra_home_blocks | customização | 20 | jun–ago/2026 | Enhancements opt-in a content blocks do core (timeline, hero). |
| decidim-government-spaces | módulo novo | 17 | jun–ago/2026 | Hierarquia Órgão→Setor com admin escopado (sem admin: true). |
| decidim-bp_meetings | customização | 14 | mai–ago/2026 | Criação, comentários e anexos controlados por etapa. |
| decidim-bp_comments | customização | 13 | jun–set/2026 | Respostas oficiais (só admin), árvore de 2 níveis, limite de 5 min, moderação. |
| decidim-bp_proposals | customização | 10 | mai–ago/2026 | Comentários por etapa; esconde setting de textos participativos. |
| omniauth-govbr | integração | 8 | jul–dez/2025 | Estratégia OmniAuth OAuth2/PKCE do gov.br. |
| decidim-categories | módulo novo | 5 | jun–ago/2026 | Categorias hierárquicas próprias (decidim_categories_categories). |
4Os diffs, repo a repo
Para cada repositório: o que é, qual o arco do seu próprio histórico (o “diff” da primeira linha ao commit fixado), e os arquivos que importam.
4.1 Customizações do upstream (sem fork)
Estes cinco estendem módulos do core do Decidim. Nenhum tem tabela própria; todos usam
prepend/include e mutação de manifest.
decidim-bp_meetings customização
Diff (14 commits, mai→ago/2026): nasceu em mai/2026 com o padrão de features isoladas; ganhou
per_step_creation, per_step_comments e anexos por etapa; terminou com o bump para 0.32.
Arquivos: lib/decidim/bp_meetings/features/{per_step_creation,per_step_comments,per_step_attachments}.rb
Marco: feat: oculta toggle global de criação de reunião (criação só por etapa).
decidim-bp_comments customização
Diff (13 commits, jun→set/2026): o mais “político” dos módulos. Respostas oficiais (só perfis administrativos respondem), árvore de dois níveis, etiqueta de resposta oficial e limite de 5 minutos para editar/excluir. No fim, passa a considerar Admin de Órgão/Setor na moderação.
Arquivos: engine.rb (prepends sobre Decidim::Comments::*),
component_settings.rb.
# engine.rb — todos os overrides são prepend sobre o core, sem fork
initializer "decidim_bp_comments.overrides" do
config.to_prepare do
Decidim::Comments::Permissions.prepend(Decidim::BpComments::CommentsPermissionsOverride)
Decidim::Comments::CommentForm.prepend(Decidim::BpComments::CommentFormOverride)
Decidim::Comments::CommentCell.prepend(Decidim::BpComments::CommentCellOverride)
# `include` e não `prepend`: o módulo só acrescenta associações de anexos.
Decidim::Comments::Comment.include(Decidim::BpComments::CommentAttachmentsOverride)
end
end
decidim-bp_proposals customização
Diff (10 commits, mai→ago/2026): o menor do conjunto. per_step_comments em propostas e
rascunhos colaborativos; esconde o setting nativo de textos participativos; reordena o checkbox de comentários.
Arquivos: lib/decidim/bp_proposals/features/per_step_comments.rb
decidim-module-questionnaires customização
Diff (71 commits, mai→set/2026 — o maior): trabalha sobre o decidim-forms (a engine de
questionários). Sem manifest novo — tudo via config.to_prepare. Numerou seções e perguntas (no
formulário, na leitura e na exportação), trouxe a tradução pt-BR e corrigiu assets na edição de respostas.
Arquivos: lib/decidim/questionnaires/engine.rb; app/ com cells, presenters,
serializers e overrides.
decidim-extra_home_blocks customização (opt-in)
Diff (20 commits, jun→ago/2026): enhancements opt-in a content blocks do core — timeline de
etapas inline, correção do hero dos espaços participativos. Cada feature vira uma setting com
default no comportamento original.
# engine.rb — não re-registra o bloco; muta o manifest já registrado
initializer "decidim_extra_home_blocks.extend_extra_data_content_block",
after: "decidim_participatory_processes.content_blocks" do
Decidim::ExtraHomeBlocks.extend_content_block(:participatory_process_homepage, :extra_data) do |manifest|
manifest.cell = "decidim/extra_home_blocks/content_blocks/extra_data"
manifest.settings do |settings|
settings.attribute :timeline_display, type: :enum, default: "inline", choices: %w(original inline)
end
end
end
4.2 Componentes/módulos novos
Aqui o time não estendeu o core — criou estrutura própria (tabelas, manifest, admin).
decidim-categories módulo novo
Diff (5 commits, jun→ago/2026): o menor em commits, mas não em ambição. Introduz uma tabela própria
(decidim_categories_categories) com hierarquia infinita (parent_id) e escopo por
espaço participativo — em vez de usar as categorias nativas do Decidim. Traz overrides para Proposals, Meetings
e votos.
Arquivos: lib/decidim/categories/{component,engine,admin_engine}.rb,
overrides/{proposals,meetings,proposals_votes}_overrides.rb.
decidim-government-spaces módulo novo
Diff (17 commits, jun→ago/2026): controle de acesso por hierarquia de governo Órgão → Setor.
Um super admin monta a hierarquia e convida admins escopados; estes administram só os processos alcançáveis
pelas suas memberships e nunca são admin: true. Acesso recalculado ao vivo →
revogar uma membership revoga o acesso na hora.
Arquivos: lib/decidim/government_spaces/participatory_processes/{space_assignment,role_config_extensions,update_process_extensions}.rb.
decidim-participatory_text componente
Diff (28 commits, abr→ago/2026): um componente de texto participativo com edição colaborativa por
parágrafo, usando Hotwire. Tem API GraphQL própria (paragraph_type), admin engine e engine própria.
Arquivos: lib/decidim/participatory_texts/{component,engine,admin_engine,api}.rb,
lib/decidim/api/paragraph_type.rb.
4.3 Integração e infraestrutura
omniauth-govbr integração
Diff (8 commits, jul→dez/2025): uma estratégia OmniAuth do zero para o gov.br. Define os escopos
(openid email profile govbr_confiabilidades), liga PKCE, e mapeia uid → sub e
o cpf no bloco info. Depois ajusta o nickname (usa name, não
sub) e remove o cpf do escopo.
# lib/omniauth/strategies/govbr.rb
option :pkce, true
option :grant_type, 'authorization_code'
option :client_options, { site: 'https://sso.staging.acesso.gov.br',
authorize_url: '/authorize', token_url: '/token',
user_info_url: '/userinfo' }
uid { raw_info['sub'] }
info do
{ sub: raw_info['sub'], name: raw_info['name'], nickname: nickname,
email: raw_info['email'], cpf: raw_info['cpf']&.gsub(/\D/, "") }
end
participa-gem (decidim-apartment) infra
Diff (56 commits, ago/2025–ago/2026): a peça que torna o multi-tenant possível. Integra o
apartment ao Decidim: um elevator que resolve o schema pelo host, o /system
fixo em public, middlewares de tenant para Sidekiq e ActiveJob, e overrides de seeds/dashboard.
# lib/decidim/apartment/elevator.rb — resolve o tenant por request
def parse_tenant_name(request)
host = request.host
return tenant_name_for_system(request) if request.path.start_with?("/system")
distribution_key = Decidim::Apartment::DistributionKey.find_by(host: host)
return "public" unless distribution_key
distribution_key.key
end
Detalhe de valor: DistributionKey é o mapa host→schema. Sem ele, um host desconhecido cai
em public — falha segura.
4.4 O fork
decidim-bp_templates (decidim-community_templates) fork
Origem: fork de decidim-ice/decidim-module-community_templates (autores Ivan Vergés e
Hadrien Froger) — o único repositório que é de fato uma cópia evoluída, não uma extensão.
Diff (39 commits, set/2025→ago/2026): funciona como upstream próprio por meses (export/import de
questionários, normalização de locales, transações git) e então, em ago/2026, é portado para Decidim
0.32.1 (port: make the module run on Decidim 0.32.1, ajuste de @use do Sass e mount do
admin engine no escopo de locale).
É o módulo que obriga o app a carregar .disabled-for-0.32/: features que dependem dele não têm
equivalente no 0.32 e ficam paradas até haver release compatível. Todo o resto do ecossistema sobreviveu ao
upgrade sem esse custo.
5Padrões transversais
Lendo os onze juntos, alguns hábitos do time ficam evidentes — e são eles que explicam por que o ecossistema aguentou o salto para 0.32.
Cada feature declara origem (issue/CA), motivo e candidatura a upstream no topo do arquivo. Intenção explícita, versionada.
Enhancements entram como setting
com default no comportamento original — ligar é escolha do admin, não surpresa.
Nove dos onze estendem o core sem copiá-lo. Só
community_templates é fork — e é o mais custoso de atualizar.
Entre ago e set/2026 quase todos receberam um
merge core/bump-32 / core: bump to 32. Foi um esforço coordenado de upgrade.
Os bp_* são deliberadamente
independentes (“sem shared lib”) — cada componente carrega a sua própria customização.
Tradução faltante e grafia pt-BR aparecem como tema recorrente dentro dos módulos, não só no app.
6Como plugam no participa
Nenhum destes módulos é publicado em RubyGems. Todos entram por git, e o Gemfile.lock
congela a revisão exata — o que torna o app reprodutível, mas também cria um ponto único de verdade:
| Ponteiro | Onde resolve | Consequência |
|---|---|---|
branch: "main" | 10 dos 11 | A branch avança; só o commit no lock te protege. |
branch: "core/bump-32" | bp_templates | Branch temporária de upgrade — sinal de que o port é recente. |
branch: "v0.1.0-rc" | participa-gem | Multi-tenant ainda em pré-release. |
Um commit bump: ou core: update modules no app muda comportamento sem nenhum diff
local. Antes de culpar o participa, confirme a revisão no Gemfile.lock e leia o
módulo naquele commit.
7Armadilhas
Os repositórios vivem sob gitlab.com/lappis-unb/decidimbr (a maioria) e
github.com/paulohtfs/omniauth-govbr. Foram alcançáveis anonimamente para o clone, mas trate-os
como dependências de terceiros: a revisão pinada é a fonte, não a branch.
community_templates segue o upstream decidim-ice por conta própria. Sem um
release compatível com 0.32, é o elo fraco do ecossistema — é o que sustenta o
.disabled-for-0.32/.
Repositório ≠ gem em vários casos (bp_templates → decidim-community_templates;
participa-gem → decidim-apartment). Ao buscar, vá pelo gemspec, não pela
pasta.
8Como ler isto
A seção 2 dá a ideia (customizar sem fork); a 3 dá o mapa; a 4 dá o detalhe por repositório; a 5–6 dão os padrões
e o encaixe no app. Este documento é o complemento de docs/participa-roadmap.html — aquele cobre o
app, este cobre os repositórios criados.
Tudo foi extraído dos clones reais feitos em
participa-gems/, cada um na revisão fixada no Gemfile.lock: git log,
gemspec, README e arquivos de lib/. Somente community_templates
foi classificado como fork a partir do homepage/autoria do seu gemspec.