Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getArticlesList,
parseArticleDate,
} from '@/features/articles/lib/articles';
import { SECTION_FLAGS } from '@/lib/section-flags';
import { SITE_URL } from '@/lib/site';

export default function sitemap(): MetadataRoute.Sitemap {
Expand Down Expand Up @@ -40,12 +41,16 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: 'monthly',
priority: 0.5,
},
{
url: `${SITE_URL}/uses`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.5,
},
...(SECTION_FLAGS.uses
? [
{
url: `${SITE_URL}/uses`,
lastModified: now,
changeFrequency: 'monthly' as const,
priority: 0.5,
},
]
: []),
...articles,
];
}
4 changes: 4 additions & 0 deletions app/uses/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Image from 'next/image';
import { notFound } from 'next/navigation';
import type { CSSProperties, ReactNode } from 'react';

import { PageWrapper } from '../components/PageWrapper';
Expand All @@ -16,6 +17,7 @@ import type { Metadata } from 'next';

import { PageTitle } from '@/base/components/PageTitle';
import { SectionHeader } from '@/base/components/SectionHeader';
import { SECTION_FLAGS } from '@/lib/section-flags';
import { SITE_NAME, SITE_URL } from '@/lib/site';

const USES_TITLE = 'Uso';
Expand Down Expand Up @@ -289,6 +291,8 @@ const AppsGrid = ({ items }: { items: App[] }) => (
);

export default function UsesPage() {
if (!SECTION_FLAGS.uses) notFound();

return (
<PageWrapper>
<main id="main">
Expand Down
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-01
79 changes: 79 additions & 0 deletions openspec/changes/archive/2026-09-01-toggle-nav-sections/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## Context

Ver `proposal.md` - Por quê. Hoje o único mecanismo existente é um `disabled: true` solto por item dentro do array `navLinks` em `src/base/components/Navbar/Navbar.tsx`, consumido separadamente pelo render desktop e pelo render mobile do mesmo componente. Não há nenhum outro arquivo lendo esse estado — `app/uses/page.tsx` e `app/sitemap.ts` não têm hoje nenhuma noção de seção "desativada".

## Goals / Non-Goals

**Goals:**
- Uma única configuração, importável por qualquer arquivo do projeto, que decide se uma seção está ativa.
- `Navbar.tsx`, `app/uses/page.tsx` e `app/sitemap.ts` todos derivam o estado de uma seção dessa mesma configuração — nunca de uma cópia local.
- Adicionar/alternar uma seção deve ser uma mudança de uma linha, num único arquivo.

**Non-Goals:**
- Não é um sistema de feature-flag genérico (sem runtime toggle, sem env var, sem painel admin) — é um interruptor estático, editado no código, para as seções de navegação do site.
- Não cria a página `/contact` nem qualquer outra rota nova.
- Não altera o comportamento de itens de menu que não têm toggle (`blog`, `me`, `now` permanecem sempre ativos, sem participar dessa configuração).

## Decisions

### Formato da configuração: objeto literal tipado, não array
```ts
// src/lib/section-flags.ts
export const SECTION_FLAGS = {
contact: false,
uses: false,
} as const satisfies Record<string, boolean>;

export type SectionKey = keyof typeof SECTION_FLAGS;
```
Um objeto por chave (`contact`, `uses`) permite `SECTION_FLAGS.uses` diretamente nos três pontos de consumo, sem precisar de `.find()`/lookup por array. `SectionKey` deriva do próprio objeto, então adicionar uma seção nova (ex.: `now: false`) já propaga o tipo para quem for usá-la — TypeScript aponta qualquer lugar que precise ser atualizado.

Alternativa considerada: array de `{ key, enabled }`. Rejeitada por exigir busca linear em todo ponto de consumo, sem ganho real sobre o objeto.

### Local do arquivo: módulo dedicado, não `src/lib/site.ts`
`src/lib/site.ts` hoje guarda apenas constantes estáticas de metadata (`SITE_NAME`, `SITE_URL`, ...). Alternar seções é uma preocupação diferente (estado de feature, não metadata do site), então fica em `src/lib/section-flags.ts` próprio — um único lugar óbvio de achar e editar, sem misturar com metadata.

### `Navbar.tsx` ganha uma chave `section` opcional por item
```ts
const navLinks = [
{ href: '/blog', label: 'Blog', icon: faNewspaper },
{ href: '/me', label: 'Sobre mim', icon: faUser },
{ href: '/now', label: 'Agora', icon: faHeadphones },
{ href: '/uses', label: 'Uso', icon: faScrewdriverWrench, section: 'uses' },
{ href: '/contact', label: 'Contato', icon: faEnvelope, section: 'contact' },
];
```
O estado "desativado" de cada item passa a ser calculado no render (`section ? !SECTION_FLAGS[section] : false`) em vez de armazenado como `disabled: true` fixo no array. Itens sem `section` (Blog, Sobre mim, Agora) nunca são desativados por essa configuração. O render desktop e mobile continuam com a mesma lógica visual de hoje ("em breve"), só troca a fonte do booleano.

Alternativa considerada: manter `disabled: true/false` direto no array, apenas movendo o valor para vir de `SECTION_FLAGS` no momento de montar o array. Rejeitada porque isso ainda deixaria o array como única fonte visível — outros arquivos (`page.tsx`, `sitemap.ts`) precisariam importar o array inteiro do componente de UI para ler um booleano, criando acoplamento estranho de "route/sitemap importa de dentro de um componente React". A chave `section` resolve isso: todos importam do módulo de config, não um do outro.

### Bloqueio de rota via `notFound()` no topo do Server Component
```ts
// app/uses/page.tsx
import { notFound } from 'next/navigation';
import { SECTION_FLAGS } from '@/lib/section-flags';

export default function UsesPage() {
if (!SECTION_FLAGS.uses) notFound();
// ...resto do componente inalterado
}
```
Segue a convenção já usada em `app/blog/[...slug]/page.tsx` e `app/og-preview/[...slug]/page.tsx` — nenhum padrão novo introduzido no projeto.

### Sitemap filtra a entrada condicionalmente
```ts
// app/sitemap.ts
...(SECTION_FLAGS.uses
? [{ url: `${SITE_URL}/uses`, lastModified: now, changeFrequency: 'monthly', priority: 0.5 }]
: []),
```

## Risks / Trade-offs

- [Alguém adiciona uma seção nova ao `navLinks` com `section` apontando para uma chave que não existe em `SECTION_FLAGS`] → Mitigado pelo tipo `SectionKey`: o TypeScript recusa compilar se a chave não existir no objeto de config.
- [Alguém muda `contact: true` no futuro sem a página `/contact` existir, e o link do menu passa a apontar para uma rota que não existe] → Fora do escopo desta mudança (a proposta já registra que `/contact` não existe); fica como responsabilidade de quem ligar essa flag no futuro, não algo que este design precisa resolver agora.
- [Esquecer de espelhar o toggle em algum dos três pontos (menu, rota, sitemap) ao adicionar uma seção nova] → Mitigado pelo próprio objetivo do design: os três pontos leem do mesmo `SECTION_FLAGS`, então não há cópia para esquecer de atualizar.

## Migration Plan

Mudança local, sem estado persistente, sem múltiplos ambientes a coordenar: as quatro edições (`section-flags.ts` novo, `Navbar.tsx`, `app/uses/page.tsx`, `app/sitemap.ts`) entram juntas em um único commit. Verificação manual pós-implementação: menu mostra "Uso" e "Contato" como "em breve"; acessar `/uses` diretamente retorna 404; `/uses` não aparece no sitemap gerado. Rollback é reverter o commit, sem migração de dados envolvida.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Por quê

O item "Contato" no menu já é exibido como desativado ("em breve"), mas esse estado hoje vive como uma flag `disabled: true` solta dentro do `Navbar.tsx`, sem nada equivalente para bloquear o acesso à página em si. A seção "Uso" precisa do mesmo tratamento de liga/desliga, e repetir essa flag ad-hoc por seção facilita esquecer uma das pontas — por exemplo, desativar o link no menu e esquecer de bloquear a rota ou remover a entrada do sitemap, deixando uma página acessível (e indexável) que o menu diz estar "em breve".

## O que muda

- Cria um único módulo de configuração dedicado, listando cada seção que pode ser ligada/desligada (começando com `contact` e `uses`) como `enabled: boolean`.
- `Navbar.tsx` passa a ler essa configuração em vez de uma flag `disabled` inline por item: uma seção desativada continua aparecendo nos menus desktop e mobile, mas renderizada como não-interativa ("em breve") — mesmo comportamento visual/UX que o "Contato" já tem hoje.
- `app/uses/page.tsx` passa a ler a mesma configuração e chama `notFound()` quando a seção `uses` está desativada, bloqueando o acesso pela própria rota (seguindo a convenção já existente de `notFound()` usada em `app/blog/[...slug]/page.tsx` e `app/og-preview/[...slug]/page.tsx`).
- `app/sitemap.ts` passa a ler a mesma configuração e omite a entrada `/uses` quando a seção está desativada, para nunca listar para indexação uma página bloqueada.
- Define `uses: false` na nova configuração como parte desta mudança, fazendo "Uso" ficar desativado ("em breve") da mesma forma que "Contato" já está. `contact` mantém seu estado atual (`contact: false`), agora vindo da configuração compartilhada em vez da flag inline.

## Capacidades

### Novas capacidades
- `site-navigation/section-toggles`: configuração central que define quais seções do menu estão ativas/desativadas, e o contrato de que a renderização do menu, o acesso à rota e a listagem no sitemap derivam todos dessa mesma fonte única de verdade.

### Capacidades modificadas
(nenhuma — ainda não há specs existentes neste repositório)

## Impacto

- Novo arquivo: `src/lib/section-flags.ts` (ou nome similar) exportando a configuração de flags das seções.
- Modificado: `src/base/components/Navbar/Navbar.tsx` (renderização dos menus desktop e mobile passa a ler a configuração compartilhada em vez da flag `disabled` inline por item).
- Modificado: `app/uses/page.tsx` (ganha uma checagem `notFound()` no topo quando desativado).
- Modificado: `app/sitemap.ts` (omite `/uses` quando desativado).
- Sem mudanças em `app/contact/*` — essa rota ainda não existe; `contact` continua representado apenas na configuração compartilhada e no menu.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Purpose

Define a única fonte de verdade que diz quais seções do site (menu de navegação) estão ativas ou desativadas, e garante que a navegação, o acesso à rota e a listagem no sitemap fiquem sempre consistentes entre si para uma mesma seção.

## ADDED Requirements

### Requirement: Configuração central de seções alternáveis
O sistema SHALL manter uma única configuração, compartilhada por todos os pontos que precisam saber se uma seção está ativa, que associa cada seção alternável (identificada por uma chave estável, ex.: `contact`, `uses`) a um estado `enabled: boolean`. Qualquer ponto do sistema que decida sobre a visibilidade do menu, o acesso à rota ou a listagem no sitemap de uma seção SHALL derivar essa decisão dessa mesma configuração — nenhum desses pontos SHALL manter seu próprio estado independente para a mesma seção.

#### Scenario: Alterar o estado de uma seção reflete em todos os pontos consistentemente
- **WHEN** o estado de uma seção alternável é alterado na configuração central (de ativado para desativado, ou vice-versa)
- **THEN** o menu de navegação, o acesso à rota da seção e a listagem dessa seção no sitemap refletem todos o novo estado, sem exigir edição em mais de um lugar

### Requirement: Seção desativada aparece no menu como "em breve"
Quando uma seção está desativada, o sistema SHALL continuar exibindo seu item no menu de navegação (desktop e mobile), porém como um elemento não-interativo (sem link navegável) que comunica visualmente que a seção está "em breve".

#### Scenario: Item de menu desativado no desktop
- **WHEN** uma seção está desativada e o usuário visualiza o menu de navegação no layout desktop
- **THEN** o item da seção aparece no menu, mas não é um link clicável e indica "em breve"

#### Scenario: Item de menu desativado no mobile
- **WHEN** uma seção está desativada e o usuário abre o menu de navegação no layout mobile
- **THEN** o item da seção aparece no menu, mas não é um link navegável e indica "em breve"

#### Scenario: Item de menu ativado é um link normal
- **WHEN** uma seção está ativada
- **THEN** seu item no menu de navegação é um link navegável normal, sem indicação de "em breve"

### Requirement: Seção desativada bloqueia acesso direto à rota
Quando uma seção com página própria está desativada, o sistema SHALL bloquear o acesso a essa página mesmo quando acessada diretamente pela URL (não apenas escondê-la do menu), respondendo como página não encontrada.

#### Scenario: Acesso direto a uma rota desativada
- **WHEN** um usuário acessa diretamente a URL de uma seção que está desativada
- **THEN** o sistema responde como página não encontrada, em vez de renderizar o conteúdo da seção

#### Scenario: Acesso direto a uma rota ativada
- **WHEN** um usuário acessa diretamente a URL de uma seção que está ativada
- **THEN** o sistema renderiza normalmente o conteúdo da seção

### Requirement: Sitemap omite seções desativadas
O sistema SHALL excluir do sitemap gerado a URL de qualquer seção que esteja desativada.

#### Scenario: Seção desativada não aparece no sitemap
- **WHEN** o sitemap do site é gerado e uma seção está desativada
- **THEN** a URL dessa seção não está presente na lista de URLs do sitemap

#### Scenario: Seção ativada aparece no sitemap
- **WHEN** o sitemap do site é gerado e uma seção está ativada
- **THEN** a URL dessa seção está presente na lista de URLs do sitemap
20 changes: 20 additions & 0 deletions openspec/changes/archive/2026-09-01-toggle-nav-sections/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## 1. Configuração central

- [x] 1.1 Criar `src/lib/section-flags.ts` exportando `SECTION_FLAGS` (`{ contact: false, uses: false }`) e o tipo `SectionKey` derivado dele; verificar rodando `yarn typecheck` sem erros

## 2. Navbar lê a configuração central

- [x] 2.1 Em `src/base/components/Navbar/Navbar.tsx`, adicionar a chave `section: SectionKey` aos itens `uses` e `contact` no array `navLinks`, remover o `disabled: true` fixo do item `contact`, e calcular o estado desativado de cada item em tempo de render a partir de `SECTION_FLAGS[section]` (itens sem `section` nunca ficam desativados); verificar rodando `yarn dev` e conferindo visualmente que "Uso" e "Contato" aparecem como itens não-clicáveis com "em breve" tanto no menu desktop quanto no mobile, e que os demais itens (Blog, Sobre mim, Agora) continuam navegáveis normalmente

## 3. Bloqueio de rota para seção desativada

- [x] 3.1 Em `app/uses/page.tsx`, importar `SECTION_FLAGS` e chamar `notFound()` no início do componente quando `SECTION_FLAGS.uses` for `false`; verificar acessando `/uses` diretamente no navegador (com `yarn dev` rodando) e confirmando que retorna a página de não encontrado

## 4. Sitemap omite seção desativada

- [x] 4.1 Em `app/sitemap.ts`, importar `SECTION_FLAGS` e incluir a entrada `${SITE_URL}/uses` apenas quando `SECTION_FLAGS.uses` for `true`; verificar acessando `/sitemap.xml` em `yarn dev` e confirmando que a URL `/uses` está ausente enquanto a flag estiver desativada

## 5. Verificação final

- [x] 5.1 Rodar `yarn lint`, `yarn typecheck` e `yarn build` e confirmar que os três passam sem erros
- [x] 5.2 Com `SECTION_FLAGS.uses` temporariamente em `true` (edição local, não commitada), confirmar que "Uso" volta a aparecer como link navegável no menu, `/uses` volta a renderizar a página normalmente, e a URL volta a aparecer no sitemap — validando que o contrato de fonte única funciona nos dois sentidos antes de deixar o valor final como `false`
32 changes: 32 additions & 0 deletions openspec/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
schema: spec-driven

# Project context (optional)
# This is shown to AI when creating artifacts.
# Add your tech stack, conventions, style guides, domain knowledge, etc.
# Example:
# context: |
# Tech stack: TypeScript, React, Node.js
# We use conventional commits
# Domain: e-commerce platform

# Per-artifact rules (optional)
# Add custom rules for specific artifacts.
# Example:
# rules:
# proposal:
# - Keep proposals under 500 words
# - Always include a "Non-goals" section
# tasks:
# - Break tasks into chunks of max 2 hours

# Per-operation guidance (optional)
# Add advisory guidance for how apply and archive work should be conducted.
# This is separate from artifact rules above.
# Example:
# operations:
# apply:
# guidance:
# - Keep test summaries concise
# archive:
# guidance:
# - Summarize the archive outcome before finishing
Loading
Loading