# ao.dev

> O portfólio que você está navegando agora — boilerplate de referência do ecossistema Nuxt 4, SSG-first, cinco morfismos de design trocáveis em tempo real, i18n completo e auditoria de performance/acessibilidade documentada passo a passo.

Este é o único case study do portfólio que descreve o próprio portfólio. `ao.dev` nasceu com uma intenção dupla: ser minha vitrine profissional e, ao mesmo tempo, servir como **boilerplate de referência** do ecossistema Nuxt 4 — um projeto real, gerado e não abandonado no meio, que qualquer pessoa possa ler de ponta a ponta e aprender algo genuíno sobre SSG, i18n, design systems trocáveis e auditoria de performance levada a sério.

Todo componente visual daqui vem do [`fui-content`](/projects/fui-content), a lib de temas que construí em paralelo. Todo case study que você já leu neste site — Leve Mix, Grupo Clamed, os módulos do Grupo 3C — foi escrito, revisado e publicado através do próprio Nuxt Content que este projeto usa. É o projeto que fecha o círculo: a ferramenta e a vitrine da ferramenta são a mesma coisa.

## Por baixo do capô

<table>
<thead>
  <tr>
    <th>
      Camada
    </th>
    
    <th>
      Tecnologia
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Framework
    </td>
    
    <td>
      <a href="https://nuxt.com/" target="_blank" rel="nofollow">
        Nuxt 4
      </a>
      
       (<code>
        compatibilityVersion: 4
      </code>
      
      , diretório <code>
        app/
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      Linguagem
    </td>
    
    <td>
      <a href="https://www.typescriptlang.org/" target="_blank" rel="nofollow">
        TypeScript
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Conteúdo
    </td>
    
    <td>
      <a href="https://content.nuxt.com/" target="_blank" rel="nofollow">
        @nuxt/content
      </a>
      
       — Markdown com frontmatter tipado via Zod
    </td>
  </tr>
  
  <tr>
    <td>
      i18n
    </td>
    
    <td>
      <a href="https://i18n.nuxtjs.org/" target="_blank" rel="nofollow">
        @nuxtjs/i18n
      </a>
      
       — <code>
        pt-br
      </code>
      
       (default) e <code>
        en-us
      </code>
      
      , estratégia <code>
        prefix_except_default
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      SEO
    </td>
    
    <td>
      <a href="https://nuxtseo.com/" target="_blank" rel="nofollow">
        @nuxtjs/seo
      </a>
      
       — schema.org, OG image, sitemap, robots
    </td>
  </tr>
  
  <tr>
    <td>
      PWA
    </td>
    
    <td>
      <a href="https://vite-pwa-org.netlify.app/frameworks/nuxt.html" target="_blank" rel="nofollow">
        @vite-pwa/nuxt
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Estado
    </td>
    
    <td>
      <a href="https://pinia.vuejs.org/" target="_blank" rel="nofollow">
        Pinia
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Estilos
    </td>
    
    <td>
      <a href="/projects/fui-content" target="_blank">
        <code>
          fui-content
        </code>
      </a>
      
       — SCSS com 5 morfismos trocáveis
    </td>
  </tr>
  
  <tr>
    <td>
      Infra dev
    </td>
    
    <td>
      Docker + Traefik, HTTPS local via <a href="https://github.com/FiloSottile/mkcert" target="_blank" rel="nofollow">
        mkcert
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      AI discoverability
    </td>
    
    <td>
      <a href="https://github.com/nuxtlabs/nuxt-llms" target="_blank" rel="nofollow">
        nuxt-llms
      </a>
      
       — <code>
        /llms.txt
      </code>
      
       gerado do conteúdo real
    </td>
  </tr>
</tbody>
</table>

O projeto roda em cima de `nuxt generate` (SSG) — o HTML de cada página é gerado uma única vez, em build time. Essa decisão de arquitetura, tomada logo no início, é a raiz de boa parte das decisões técnicas mais interessantes do projeto: tudo que normalmente seria "decisão em runtime" (breakpoint, tema, idioma) precisa ser resolvido sem um servidor por trás re-renderizando a cada request.

## i18n — dois campos, dois formatos, uma convenção

O detalhe mais fácil de errar em i18n multi-locale é misturar dois conceitos que parecem a mesma coisa: o **prefixo de URL** e a **tag de idioma HTML**. O projeto separa os dois explicitamente:

<table>
<thead>
  <tr>
    <th>
      Campo
    </th>
    
    <th>
      Valor
    </th>
    
    <th>
      Responsabilidade
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        code
      </code>
    </td>
    
    <td>
      <code>
        pt-br
      </code>
      
       / <code>
        en-us
      </code>
    </td>
    
    <td>
      Prefixo de URL — sempre lowercase (convenção HTTP, <a href="https://www.rfc-editor.org/rfc/rfc3986" target="_blank" rel="nofollow">
        RFC 3986
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        language
      </code>
    </td>
    
    <td>
      <code>
        pt-BR
      </code>
      
       / <code>
        en-US
      </code>
    </td>
    
    <td>
      <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt" target="_blank" rel="nofollow">
        BCP 47
      </a>
      
       — HTML <code>
        lang
      </code>
      
      , hreflang, meta tags
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        file
      </code>
    </td>
    
    <td>
      <code>
        pt-BR.json
      </code>
      
       / <code>
        en-US.json
      </code>
    </td>
    
    <td>
      Caminho de filesystem — não é URL, mantém uppercase
    </td>
  </tr>
</tbody>
</table>

A regra existe porque `nuxt-link-checker` (parte do `@nuxtjs/seo`) reporta `no-uppercase-chars` para qualquer path com letra maiúscula — trocar o `code` pra lowercase elimina o warning na raiz, sem precisar de `skipInspections` como gambiarra.

### Nomes de coleção — derivados algoritmicamente, nunca por ternário

Com quatro coleções por tipo de conteúdo (`blog_ptBR`, `blog_enUS`, `projects_ptBR`, `projects_enUS`...), o padrão ingênuo — `locale.value === 'en-us' ? queryCollection('blog_enUS') : queryCollection('blog_ptBR')` — não escala: com um terceiro idioma vira uma cadeia de `if/else` espalhada por dezenas de arquivos. A solução foi um composable que deriva o sufixo algoritmicamente a partir do `code`:

```ts
// app/composables/useLocaleCollection.ts
// pt-br → ptBR · en-us → enUS · es-mx → esMX
// (split '-', partes após o hífen viram UPPERCASE, join sem separador)

const { blogCol, pathPrefix } = useLocaleCollection()
queryCollection(blogCol.value).all()
```

Adicionar um quinto idioma vira: estender uma type union num único arquivo, criar as coleções em `content.config.ts`, criar a pasta de conteúdo. Zero mudança nos composables de listagem ou nas páginas — nenhum novo ternário nasce.

## SSG-first — nenhuma decisão de layout em JavaScript

Como o HTML é gerado uma única vez, `useViewport()` para decisão de layout visual é proibido no projeto: no SSR/SSG, o hook usa um valor de fallback, não o viewport real do visitante — a hidratação então descobre o viewport de verdade e troca o layout debaixo do usuário, gerando CLS garantido.

A regra prática:

- Responsividade **sempre** via media query CSS (`fui-col-md-6`, `fui-col-lg-4`) — o browser aplica depois de já ter o viewport real, sem mismatch.
- Contagem de itens, tamanhos e visibilidade condicionados a breakpoint **nunca** em JS de componente de listagem.
- `perPage` fixo (31, hoje) em vez de variar por viewport — quem precisa de menos itens por página troca via select, não via detecção automática.
- Única exceção: interação puramente client-side (hover, foco, animação) pode usar JS normalmente — não afeta o HTML já pintado.

## Morfismos — o FOUC que o SSG cria sozinho

A lib `fui-content` foi desenhada pra instalar **um** morfismo por projeto — os seletores que ela emite são flat, sem prefixo (`.fui-btn {}`, não `.fui-glass .fui-btn {}`). O ao.dev quebra essa premissa de propósito: carrega os cinco temas como arquivos CSS separados e troca via `<link id="fui-theme">`, pra dar ao visitante o seletor de morfismo em tempo real. Isso cria dois problemas que a lib, sozinha, nunca precisaria resolver.

### Problema 1 — FOUC em SSG

`useCookie` não tem contexto de request durante o prerender: o HTML gerado sai sem classe de morfismo e sem `<link>` de tema. O Vue hidrata, `useMorphism` lê o cookie do visitante, adiciona a classe e o link — e nesse intervalo o usuário vê um flash de estilo sem tema nenhum.

### Problema 2 — bleed entre temas

Sem escopar os seletores, `.fui-btn {}` de um tema carregado vaza pro documento inteiro, mesmo que o morfismo ativo seja outro.

### A solução — PostCSS scoping + script bloqueante

`scripts/build-themes.ts` roda um passo de PostCSS depois de cada compilação SCSS, prefixando todo seletor com `html.{tema}`:

```scss
/* antes (emitido pela lib, pensada pra single-theme) */
:root { --fui-color-primary: #7c3aed; }
.fui-btn { padding: var(--fui-space-3); }

/* depois do PostCSS scoping, por tema */
html.skeu { --fui-color-primary: #7c3aed; }
html.skeu .fui-btn { padding: var(--fui-space-3); }
```

E no `<head>`, um script inline com `tagPriority: 'critical'` roda antes de qualquer paint: lê o cookie via regex, valida contra a lista de morfismos conhecidos, adiciona `html.{morfismo}` via `classList.add` (idempotente — no SSR a classe já está lá) e injeta um `<link rel="preload" as="style">` que vira `stylesheet` no `onload`. No SSR o servidor já renderiza a classe certa — zero FOUC automaticamente. No SSG, sem cookie no HTML, o script assume o trabalho no client, antes do primeiro paint.

> Lighthouse testa sem cookies — o script é no-op, nenhum CSS de tema carrega, zero impacto em Performance.

### O bug real — ordem de `@layer` num reload frio

Esse foi o bug mais difícil de diagnosticar do projeto inteiro. Trocar de morfismo funcionava perfeitamente client-side — `setMorphism()`, sem reload. Mas um **reload real** com o cookie já setado causava bleed visual: border-radius, padding, gap, font-size e box-shadow trocando de valor de forma inconsistente, elemento por elemento.

A causa raiz: cada arquivo de tema gerado (`public/themes/{skeu,glass,neu,clay}.css`) só declarava blocos soltos de `@layer fui.components {}` / `@layer fui.utilities {}` — só o bundle base declarava a ordem canônica completa (`@layer fui.layout, fui.base, fui.components, fui.utilities;`). Numa corrida de rede real (o script inline injeta o `<link>` do morfismo muito cedo), se o browser parseasse o `<link>` do tema **antes** do bundle base, a ordem global de registro dos layers saía errada — e o reset cru do base passava a vencer utilitários e componentes do morfismo.

O fix: prefixar cada arquivo de tema gerado com a **mesma** declaração de ordem do base, antes de qualquer seletor. Não importa qual arquivo o browser parseia primeiro — a ordem correta é estabelecida de cara por qualquer um dos dois, e a segunda declaração idêntica é um no-op. Elimina a corrida de rede estruturalmente, sem depender de timing de `<link>`.

## Hidratação — quando o próprio módulo de SEO causava o mismatch

Outro bug que só aparecia em SSG, nunca em dev: `Hydration completed but contains mismatches.`, sempre no mesmo lugar — um nó de texto dentro de um `<style>` do body.

O culpado era `nuxt-seo-utils` (incluído via `@nuxtjs/seo`), que tem `minify: true` por padrão. O plugin Nitro de minificação de HTML comprime **todos** os `<style>` inline durante o prerender — incluindo o `<style>` que o Shiki/MDC injeta no body com as CSS vars de syntax highlighting. Resultado: o HTML do servidor tinha CSS minificado, o payload Nuxt tinha CSS não-minificado (vindo direto do SQLite do `@nuxt/content`), e o cliente re-renderizava o `MDCRenderer` usando o payload — CSS diferente, mismatch garantido.

```ts
// nuxt.config.ts
seo: {
  minify: { build: false }, // só desativa a minificação de body no prerender —
},                          // runtime (head tags via unhead) continua ativo e não causa mismatch
```

Debugar esse tipo de mismatch em produção (SSG) tem uma flag pouco conhecida do Vue 3.4+ que ativa mensagens detalhadas mesmo em build de produção:

```ts
vite: {
  define: {
    __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(true),
  },
}
```

## Auditoria de performance — números reais, não estimativa

Uma auditoria pré-deploy na home, usando Lighthouse a sério (não só rodando uma vez e aceitando o número), revelou uma série de problemas de sistema — coisas que valem pra qualquer página nova, não só a que foi testada.

### Compressão que o preset de produção não fazia sozinho

`compressPublicAssets` é `false` por padrão no Nitro. Sem essa config, o preset `node-server` (o que roda em produção SSR) serve `_nuxt/*.css`/`*.js`/fontes **sem gzip nem brotli** — chegamos a medir o mesmo arquivo CSS **15× maior** em SSR (1,28 MB) do que num servidor estático genérico (83 KB) servindo o mesmo build.

```ts
nitro: {
  compressPublicAssets: { gzip: true, brotli: true },
},
```

### Fetch de componente global só na interação, nunca no mount

O `CommandPaletteComponent` (busca via ⌘K) fica montado em `app.vue` em **toda página**, mesmo fechado. Ele chamava `useAsyncData(..., { server: false, lazy: true })` — mas `lazy: true` só adia o *render*, o fetch dispara imediatamente após a hidratação de qualquer jeito. Isso instanciava o motor de busca do `@nuxt/content` (SQLite-WASM client-side) em toda página, mesmo que o visitante nunca abrisse a busca: medimos 1–2,4s de scripting não-atribuído no bootup e 239–404 KiB de JS não usado.

A correção: `immediate: false` no `useAsyncData`, com `execute()` disparado manualmente só quando o usuário efetivamente abre a busca.

<figure className="rehype-figure">

![Command Palette (⌘K) aberta, com sugestões de projetos, posts e tags puxadas do conteúdo real antes mesmo de digitar](/images/pages/projects/ao-dev/ao-dev-command-palette.png)<figcaption>

Command Palette (⌘K) aberta, com sugestões de projetos, posts e tags puxadas do conteúdo real antes mesmo de digitar

</figcaption>
</figure>

### Purge de CSS não usado

A lib `fui-content` é utility-first — gera um conjunto fixo de classes em compile-time, sem noção de quais componentes `.vue` deste projeto realmente usam o quê. `@fullhuman/postcss-purgecss`, configurado no `nuxt.config.ts` (nunca na lib), resolve isso como uma otimização de build equivalente ao tree-shaking de JS:

<table>
<thead>
  <tr>
    <th>
      Métrica
    </th>
    
    <th>
      Antes
    </th>
    
    <th>
      Depois
    </th>
    
    <th>
      Redução
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Tamanho bruto
    </td>
    
    <td>
      1,28 MB
    </td>
    
    <td>
      143 KB
    </td>
    
    <td>
      −89%
    </td>
  </tr>
  
  <tr>
    <td>
      Tamanho gzip
    </td>
    
    <td>
      ~113 KB
    </td>
    
    <td>
      ~21,6 KB
    </td>
    
    <td>
      −81%
    </td>
  </tr>
  
  <tr>
    <td>
      Classes CSS
    </td>
    
    <td>
      14.508
    </td>
    
    <td>
      464
    </td>
    
    <td>
      —
    </td>
  </tr>
</tbody>
</table>

O risco real de um purge automático é classe montada dinamicamente — ``fui-tag-${tech.color}`` nunca aparece como string completa no código-fonte, então o scanner erraria e apagaria a classe. Antes de ativar, uma varredura por interpolação de template literal com prefixo `fui-` identificou os poucos casos reais, que entraram numa safelist por regex (`/^fui-tag-/`). O resultado foi validado com rebuild + screenshot + computed styles em três páginas antes de considerar fechado — nunca só "o build passou sem erro".

### Prioridade de LCP por página

O elemento de LCP muda por página — na home é o primeiro card de morfismo, em outra pode ser uma imagem de capa. Só o elemento real de LCP recebe `fetchpriority="high"` + preload; aplicar em várias imagens dilui a prioridade e não ajuda em nada.

```html
<NuxtImg
  :preload="isLcp ? { fetchPriority: 'high' } : undefined"
  :fetchpriority="isLcp ? 'high' : undefined"
  ...
/>
```

## `queryCollection` — payload é orçamento, não detalhe

O campo `body` de uma coleção `type: 'page'` é o AST completo do Markdown — 30 a 200 KB por documento. Uma query de listagem sem `.select()` inclui o `body` de **todos** os posts, multiplicado por rota SSG.

```ts
// ERRADO — body de todos os posts (~770 KB por rota)
queryCollection(blogCol.value).order('date', 'DESC').all()

// CORRETO — só frontmatter (~5–20 KB)
queryCollection(blogCol.value)
  .select('path', 'title', 'shortTitle', 'description', 'date', 'readTime', 'tags', 'cover')
  .order('date', 'DESC')
  .all()
```

Duas regras derivadas: **indexes** em `content.config.ts` para toda coluna usada em `.where()`/`.order()` (obrigatório em bancos como Cloudflare D1, onde cada linha lida é billing); e chaves de `useAsyncData` **sempre** delimitadas por `:`, nunca `-`, quando combinam prefixo literal com slug dinâmico — o cache de `useAsyncData` é global no app, e `blog-${slug}` colide estruturalmente entre um post `"prev-foo"` e outro post `"foo"` cuja chave também vira `blog-prev-foo-...`. Com `:` como delimitador, a colisão é impossível, porque slug de conteúdo nunca contém `:`.

## Acessibilidade — o que o IBM Equal Access ensinou

Ciclos reais de auditoria com o [IBM Equal Access Checker](https://www.ibm.com/able/toolkit/tools/) corrigiram padrões que pareciam corretos até serem testados de verdade:

- **ARIA combobox é ARIA 1.2** — `role="combobox"` vai no `<input>`, nunca num `<div>` wrapper; o input precisa ser `type="text"`, porque `type="search"` já carrega role implícito `searchbox` e não aceita override.
- **<p> com bold dentro de link dispara heurística de heading falso** — IBM interpreta `<p class="fui-fw-bold">` dentro de `<a>` como possível heading mal-formado. Trocar por `<span class="fui-d-block">` preserva o visual sem acionar a regra, porque span é inline.
- **aria-labelledby nunca aponta pra elemento condicional ausente** — se o alvo é `v-if`, o valor cai pra `undefined` quando esse elemento não existe no DOM.
- **new Date() como fallback em template é hydration mismatch garantido** — o timestamp do server nunca bate com o do client; a correção é `v-if` pra omitir o elemento, nunca gerar uma data "de qualquer jeito".

> Nem todo achado do IBM é bug real. `aria_keyboard_handler_exists` dispara porque a ferramenta não enxerga `@keydown` do Vue (só `onkeydown` HTML puro) — falso positivo documentado, não "corrigido" às cegas.

## Descoberta por IA — `/llms.txt` gerado, nunca hardcoded

O site expõe `/llms.txt` e `/llms-full.txt` ([padrão da comunidade](https://llmstxt.org)) via `nuxt-llms` — alternativa gratuita ao equivalente pago do Nuxt SEO PRO. Cada seção do arquivo é gerada a partir de uma coleção real (`blog_ptBR`, `projects_enUS`...), nunca de um template estático — o arquivo nunca fica desatualizado porque não existe versão "escrita à mão" pra ficar velha.

A mesma lib expõe o Markdown cru de cada página de conteúdo via `/raw/<path>.md` — é o link "Ver como Markdown" que aparece no topo de cada post de blog e case study, inclusive neste que você está lendo agora.

<figure className="rehype-figure">

![Cabeçalho de um case study com o link "Ver como Markdown" visível, expondo o conteúdo real via /raw/<path>.md](/images/pages/projects/ao-dev/ao-dev-ai-actions.png)<figcaption>

Cabeçalho de um case study com o link "Ver como Markdown" visível, expondo o conteúdo real via /raw/<path>.md

</figcaption>
</figure>

## O checklist de finalização — cinco fases, sempre nessa ordem

Nenhuma página nova (ou refatoração significativa) é considerada pronta sem passar, em ordem, por:

1. **Lint + typecheck** — `lint:fix`, `lint:css:fix`, `typecheck`
2. **Auditoria SSG** — `nuxt generate` + preview estático, WAVE (visual) + IBM Equal Access (relatório exportado) em PT-BR/EN-US × light/dark
3. **Auditoria SSR** — `nuxt build` + `nuxi preview`, mesma dupla de ferramentas, mais atenção a hidratação e Schema.org no HTML inicial
4. **Logs de build** — payloads acima de 50 KB são investigados, não ignorados
5. **Lighthouse** — a melhor nota possível em Performance, Acessibilidade, Boas Práticas e SEO, com LCP, INP e CLS dentro dos limites recomendados pelo Core Web Vitals, nas duas dimensões (desktop/mobile) × dois temas (light/dark)

O `throttlingMethod: simulate` padrão do Lighthouse tem variância entre execuções — a mesma página, sem nenhuma mudança de código, pode sair um pouco diferente de uma rodada pra outra, dependendo da carga da máquina no momento do teste. Persigo a nota mais alta possível em toda página; a régua real de aceite é sempre o resultado mais recente rodado nas condições descritas acima.

Só depois de todos os cinco passos passarem uma página entra em commit. A regra existe porque Core Web Vitals, neste projeto, não é bônus — é requisito, no mesmo nível que lint ou typecheck.

## A listagem de projetos — o design system em produção

<figure className="rehype-figure">

![Listagem de projetos em grid, com filtro por categoria e vários cards usando o mesmo componente de card](/images/pages/projects/ao-dev/ao-dev-projects-listing.png)<figcaption>

Listagem de projetos em grid, com filtro por categoria e vários cards usando o mesmo componente de card

</figcaption>
</figure>

Essa é a página mais honesta pra ver o `fui-content` funcionando em escala: dezenas de cards, todos o mesmo componente (`ProjectCardGridComponent`), cada um puxando `cover` do frontmatter real de cada projeto, com busca, filtro por categoria e contagem dinâmica — toda a lógica de listagem vive nos composables (`useProjectsListing`), zero duplicação entre a versão grid e a versão lista.

## Nav mobile e busca global

Fora do desktop, a navegação principal vira bottom tabs fixas (Início, Projetos, Buscar, Blog, Contato) mais um menu hambúrguer pro resto dos links — nunca decidido por `useViewport()` (pela mesma regra SSG-first já descrita), sempre por CSS puro reagindo ao viewport real do dispositivo. Isso é bem mais fácil de sentir ao vivo do que numa captura de tela redimensionada no desktop — se você está lendo isto num notebook, vale abrir `ao.dev.br` direto no celular pra ver a barra de tabs de verdade, no dispositivo de verdade.

## O que ainda falta

Duas frentes seguem abertas, deliberadamente não resolvidas ainda:

- **Migração @nuxtjs/mdc → Comark** — o `@nuxtjs/mdc` está em deprecação confirmada pelo próprio time que o mantém, em favor do Comark ("faster, AI-friendly, and no longer bound to Vue or Nuxt"). O `@nuxt/content` ainda depende da versão antiga; a migração real só chega via update de dependência, com guia oficial de breaking changes — não faz sentido instalar o Comark manualmente antes disso.
- **@nuxt/a11y** — removido de `modules:` por bugs reais documentados (conflito de versão com `@nuxt/devtools-kit`, erros de RPC silenciados no próprio módulo), mas mantido no `package.json` — reativação é um ciclo de investigação dedicado, não um ajuste de última hora perto de deploy.

Este case study fecha um ciclo estranho de escrever: é o único projeto deste portfólio cujo "cliente" sou eu mesmo, cujo "deploy" ainda não aconteceu no momento em que escrevo isto, e cuja auditoria de qualidade se aplica ao próprio texto que você está lendo. O primeiro post do blog e o deploy em `ao.dev.br` vêm logo depois deste commit.
