Módulo de navegador: resumo da campanha
Pacote completo com ação na mesa, leitura autorizada da API e modal nativo.

Objetivo e pré-requisitos
Adicione o botão Campaign summary em table.interface. Ao clicar, ele lê somente os atores visíveis ao usuário conectado e mostra o resultado no modal existente.
Host alvo: 0.3.0, SDK 1.0.0. Textos da interface do exemplo estão em inglês. Preserve a versão solicitada ao adaptar.
Arquivos completos
Estes são fontes completos, sem assinatura. Depois de revisar o código, teste por Instalar de ZIP em um host descartável. Para publicar pelo Marketplace, prepare a assinatura e o catálogo da referência de assinatura e ativação. Nenhuma chave privada acompanha o download.
Manifesto completo
{
"id": "example.campaign-summary",
"name": "Example campaign summary",
"version": "1.0.0",
"sdk": {
"requires": ">=1.0.0 <2.0.0",
"tested": "1.0.0"
},
"description": "Adds an authorized read-only summary in a native modal.",
"author": "Gravewright documentation examples",
"license": "GPL-3.0-only",
"entry": "main.mjs"
}Código do exemplo
main.mjs
export default {
start({styles}) { styles.use('styles.css'); },
register({register}) {
register('table.interface', ({root, api, signal, onDispose}) => {
const doc=root.ownerDocument;
const panel=doc.createElement('section');
panel.className='example-panel';
const button=doc.createElement('button');
button.type='button';
button.textContent='Campaign summary';
const status=doc.createElement('p');
status.setAttribute('role','status');
panel.append(button,status);
root.append(panel);
let modal;
button.addEventListener('click', async () => {
button.disabled=true;
try {
const actors=await api.actors.list({}, {signal});
if(signal.aborted) return;
if(modal) await modal.close();
if(signal.aborted) return;
const content=doc.createElement('section');
content.setAttribute('role','dialog');
content.setAttribute('aria-modal','true');
content.setAttribute('aria-label','Campaign summary');
const text=doc.createElement('p');
text.textContent='Readable actors: '+actors.filter(actor=>actor.canOpen).length;
const close=doc.createElement('button');
close.type='button';close.textContent='Close';
content.append(text,close);
modal=api.ui.openModal(content);
close.addEventListener('click',()=>void modal.close(),{signal});
status.textContent='Summary loaded.';
} catch(error) {
if(!signal.aborted) status.textContent=error.code || 'unavailable';
} finally { if(!signal.aborted) button.disabled=false; }
},{signal});
onDispose(()=>{panel.remove();});
});
},
stop() {}
};styles.css
.gw-module-root .example-panel { padding: var(--space-4, 16px); font-size: 14px; }
.gw-module-root .example-panel label { display: block; margin-bottom: var(--space-3, 12px); }
.gw-module-root .example-panel button { margin-right: var(--space-2, 8px); }
.gw-module-root[data-module-id="example.campaign-summary"] .example-panel { position: fixed; left: 16px; bottom: 112px; z-index: 20; max-width: min(360px, calc(100vw - 32px)); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); color: var(--text); pointer-events: auto; }Como executar
Revise o ZIP-fonte e use Módulos → Instalar de ZIP em um host descartável; depois habilite a versão exata na campanha.
Abra a mesa e clique em Campaign summary. Um modal nativo mostra a contagem de atores legíveis. Feche pelo botão ou Escape.
Repita como jogador com acesso a menos atores. Abra/feche novamente e desative o módulo durante uma leitura pendente.
Resultado esperado e ciclo de vida
GM e jogador podem receber contagens diferentes. O exemplo não grava dados. O host libera o modal e gerencia foco/fundo; o descarte da montagem remove o painel e cancela seus listeners.
Erros e limites
permission_denied indica falta de acesso; stale_context indica montagem/ativação encerrada. Falhas de rede aparecem no parágrafo de status. O botão fica desabilitado durante a leitura para impedir requisições duplicadas.
Como validar
Compare a contagem com o diretório de atores daquele usuário.
Confira que Escape devolve o foco e os controles nativos continuam disponíveis.
Saia da mesa ou desative durante o carregamento; nenhum modal deve abrir depois.
Como usar a IA neste exemplo
Adapte o exemplo campaign-summary para mostrar [métrica autorizada somente leitura]. Preserve table.interface, a API do contexto, o modal nativo e o cancelamento/descarte. Confira assinaturas na release alvo e teste resultados de GM/jogador.