Pular para conteúdo

0045 — Assuntos, navegação e a versão certa para cada aparelho

Status CONCLUÍDA
Branch spec/0043-grupos-e-navegacao-na-ajuda (nome anterior à renumeração)
Criada em 2026-08-23
Apps afetados web · e2e · skill /tutoriais · scripts/preflight.py (sem impacto na api)
Revisão do problema Aprovada na rodada 4 (seção 6)
Revisão da experiência Aprovada na rodada 6 (seção 8)
Revisão do detalhamento Aprovada na rodada 15 (seção 16)
Revisão da implementação —

Status: RASCUNHO → APROVADA → EM IMPLEMENTAÇÃO → CONCLUÍDA

A spec tem três partes, escritas em ordem, e quatro portões — cada um revisado por um subagente de contexto limpo, em até 3 rodadas. A máquina de revisão é do plugin superpowers; a régua é a rubrica correspondente em .claude/rubrics/. Achados vêm classificados: qualquer Critical reprova, Important em aberto reprova salvo decisão registrada do usuário, e Minor vira pendência aceita.

  1. A Parte I diz qual é o problema e como se convive com ele hoje. Revisada na seção 6 (necessidade × jornada de hoje). É o portão mais barato de todos, e o único que pergunta contra que problema isto está sendo construído.
  2. A Parte II diz como se usa o que vai existir. Revisada na seção 8 (jornada × viewports).
  3. A Parte III diz como se constrói. Revisada na seção 16 (segurança × clareza técnica) — autorização, isolamento, e se sobrou decisão para quem vai codar.
  4. A implementação é medida contra as três, na seção 17 (aderência × qualidade × testes) — uma revisão por fase, e uma da branch inteira antes de fechar.

Cada parte só se escreve depois que a anterior passou. Descrever tela sobre problema mal enquadrado, ou detalhar endpoint sobre experiência vaga, é o jeito mais caro de descobrir que era outra coisa.


Parte I — O problema

Aqui não entra solução: nem tela, nem endpoint, nem botão. Só o que dói, para quem, e o que se faz hoje. Se você já sabe qual é a tela, guarde para a Parte II.

1. Dor

Quem precisa aprender não acha o tutorial certo, e de um tutorial não chega ao seguinte. A Ajuda mostra todos numa grade única, ordenada por papel — achar o seu é varrer —, e a página de um tutorial não leva a nenhum outro: quem termina de assistir volta para a grade inteira ou fecha a aba.

Hoje isso funciona, e é honesto dizer por quê: há dois tutoriais publicados. Dois cabem na tela, dispensam agrupamento e não dão o que navegar. O problema não é o que a tela faz com dois — é que a estrutura que decide o que ela fará quando forem muitos está sendo escrita agora, um tutorial de cada vez, e cada um que nasce sem o assunto ligado à tela é mais um a preencher depois.

Quatro coisas ficam impossíveis, erradas ou manuais enquanto a Ajuda não conhece grupo:

  • Não há como dizer a que assunto um tutorial pertence. O catálogo que a tela lê — apps/web/src/tutoriais/<slug>.json — não tem campo de grupo. A informação existe, mas só do lado de fora: em videos/publicacoes.json, que organiza as playlists do YouTube e que o app deliberadamente não lê (spec 0042). O produto sabe menos sobre o próprio material didático que o canal de vídeo sabe.

E não, o campo publico que já existe não responde a isso — a pergunta é natural, porque nos dois tutoriais publicados os dois coincidem (AGENCY_USER ↔ Tutorial Imobiliárias Parceiras, ADMIN_TENANT ↔ Tutoriais Gestão de Imobiliárias). A coincidência é de amostra, não de natureza: publico diz a quem o tutorial serve; grupo diz de que assunto ele trata, e as duas perguntas se separam assim que o material cresce.

A prova está no nome do grupo que já existe. Gestão de Imobiliárias nomeia um assunto, não um papel — e os próximos tutoriais da administração (parâmetros da garantidora, equipe e perfis, tipos de documento) têm o mesmo ADMIN_TENANT e não pertencem a ele. Um público, vários assuntos. Derivar o grupo do público faria esses tutoriais caírem todos numa gaveta chamada "administração", que é precisamente a varredura que a seção 3 descreve, só que com um rótulo em cima.

A ordenação por publico foi a resposta anterior a este mesmo incômodo, e está implementada (RN7 e RN8 da spec 0042): quem entra vê primeiro o que serve ao seu perfil. Ela ajuda e não basta — promove um conjunto para o topo, mas continua entregando uma lista corrida, sem dizer onde um assunto termina e outro começa. Com dois tutoriais isso não se nota; é a limitação que esta spec ataca, e não um defeito daquela. - A informação existe e não chega onde é usada. O grupo é decidido por quem produz e fica gravado — em videos/publicacoes.json, versionado, e os dois tutoriais existentes já o têm lá. O que não existe é o caminho dele até o catálogo que a tela lê.

Isto enfraquece de propósito o argumento de urgência, e uma versão anterior desta seção o exagerava dizendo que o grupo "some": como o dado está guardado, preencher o catálogo depois seria transporte mecânico de um campo, não reabrir roteiro por roteiro para redecidir. O custo de adiar é real e pequeno, e fica registrado assim. O que sustenta fazer agora não é o custo de adiar — é que a decisão de declarar o assunto uma vez só (seção 4) só produz efeito nos tutoriais que ainda vão ser escritos, e eles estão sendo escritos. - Um tutorial só pode ter um vídeo, e o produto tem duas interfaces. O Garantora é usado no desktop e no celular, e é instalável como PWA — o mobile não é uma concessão, é um caminho de uso próprio. As telas diferem de verdade: no celular a navegação é gaveta em vez de menu lateral, a lista é cartão em vez de tabela, as ações do topo viram ícone. Um tutorial gravado no desktop mostra passos que não existem no celular, e vice-versa.

O catálogo, porém, tem um youtubeId por tutorial. Ensinar os dois caminhos hoje obriga a criar duas entradas que se parecem demais — mesmo título, mesma frase, mesmo assunto — e a lista dobra de tamanho para dizer a mesma coisa duas vezes. É o que aconteceria: uma lista em que metade dos itens repete a outra metade não é uma lista maior, é a mesma lista pedindo o dobro de leitura.

Isto ainda não dói, e é honesto dizer: os dois tutoriais existentes foram gravados no desktop, e ninguém pediu a versão de celular ainda. O que torna a decisão urgente é a mesma razão da anterior — ela vale para o material que ainda vai ser produzido, e ele está sendo produzido.

  • E há um risco de desenho a evitar, que não é consequência de hoje mas decorre da correção. O que está na Ajuda é o que está no YouTube: mesmo tutorial, mesmo título, mesma descrição, mesmo assunto — a tela apenas embute o vídeo que o canal hospeda. A saída óbvia para as consequências acima seria declarar o assunto também no catálogo, e aí a mesma organização passaria a existir em dois arquivos sem nada conferir que concordam. É a classe de dívida que a spec 0042 pagou entre o roteiro e o catálogo, e a lição de lá foi que duplicação sem verificação diverge no primeiro esquecimento. Fica registrado aqui porque é o jeito mais fácil de "resolver" isto e sair pior.

Quantos tutoriais virão não está escrito em lugar nenhum, e este documento não inventa o número: não há fila de roteiros no repositório nem lista de processos previstos. O que se sabe é que a produção está em curso, que dois foram feitos em dois dias e que o sistema tem processos sem tutorial — análise pelo lado do analista, emissão de garantia, cobrança. O cenário contra o qual esta spec desenha é esse, e é declarado como cenário, não como previsão.

Não há dor medida, e não há como haver. O produto não tem cliente (ver CLAUDE.md), a tela de Ajuda subiu para produção ontem, e ninguém assistiu a nada em uso real. Qualquer número aqui seria inventado. O que sustenta esta spec não é volume observado: é que a decisão de estrutura está sendo tomada de qualquer maneira, a cada tutorial gravado, e a alternativa a tomá-la agora é tomá-la por omissão.

2. Quem sente

Todos os papéis autenticados, porque a Ajuda não filtra por papel: quem entra vê todos os tutoriais, e a ordenação por publico apenas promove os do seu perfil (RN7 e RN8 da 0042). Não há papel que escape da grade única.

A ordem em que a falta pesa acompanha o tamanho do material de cada público:

  • Corretor de imobiliária parceira — derivado do modelo de negócio, não de uso observado: a imobiliária é o canal (ver CLAUDE.md, Domínio), então ela atravessa o processo inteiro de pedir a garantia e o faz entre um aluguel e outro, sem conviver com o sistema todo dia. Disso decorre que é o público com mais processo a aprender e o que menos acumula familiaridade. Nada disso foi medido em uso.
  • Equipe da garantidora — analista, financeiro e administração. Cada área tem o seu conjunto, e um conjunto misturado com o das outras é ruído: quem vai aprender a decidir uma análise não precisa varrer os tutoriais de cadastro de imobiliária para achar o seu.
  • Quem produz os tutoriais — o time. Sente antes de todo mundo e por dois motivos: é quem decide o assunto e declara a mesma coisa duas vezes enquanto os dois lugares não forem um só; e é quem vai gravar duas vezes cada processo, uma por interface, sem ter onde dizer que as duas gravações ensinam a mesma coisa.

E há um público que o desenho atual não enxerga: quem usa pelo celular. O Garantora é instalável como PWA, e o corretor em visita ao imóvel é o caso de uso que mais justifica isso. Hoje ele receberia um tutorial gravado numa tela que não é a dele — com um menu lateral que na gaveta dele não existe, e uma tabela que no aparelho dele é cartão. Não é material pior: é material de outra interface.

É caminho obrigatório, não exceção, para quem chega sem saber usar o produto — que é justamente a razão de a Ajuda existir. Não há volume a medir, pelo motivo da seção 1.

3. A jornada de hoje

A resposta honesta é que não há jornada instalada, e a spec se apoia nisso em vez de disfarçar. O material didático está sendo produzido agora; ninguém de fora do time assistiu a um tutorial ainda. Descrever passos que ninguém deu seria inventar.

O que existe e é conferível é o comportamento do sistema hoje, e é ele que a Parte II vai mudar:

  1. A pessoa abre /ajuda pelo item no rodapé do menu.
  2. Vê uma grade única com todos os tutoriais publicados — hoje dois — em cartões de miniatura, título, frase e duração. Os que servem ao papel dela vêm primeiro; os demais, depois. Não há título de seção, filtro nem busca: a leitura é varrer os cartões.
  3. Abre um tutorial. Assiste, com o roteiro em passos ao lado.
  4. Termina, e a página acaba ali. As saídas são "Ir para a tela" — que leva ao lugar onde ele faz o que aprendeu — e "Todos os tutoriais", que devolve à grade inteira. Não há "o próximo deste assunto", porque a página não sabe que existe assunto.
  5. Para achar o seguinte, ela repete o passo 2: volta à grade.
  6. E se abriu tudo isso pelo celular, viu a mesma coisa que veria no computador. A tela se adapta — a grade vira coluna única, o vídeo empilha sobre o roteiro —, mas o conteúdo do vídeo é o mesmo: os dois tutoriais publicados foram gravados no desktop, e é o que toca em qualquer aparelho. Quem assiste no telefone vê um menu lateral que na gaveta dele não existe e uma tabela que no aparelho dele é cartão. Nada na tela diz que aquilo foi gravado noutra interface, então não há como saber se é assim mesmo ou se ele está no lugar errado.

Os passos 1 a 4 são o que o sistema faz hoje, conferível abrindo a tela. O passo 5 é observável como comportamento — a única saída é a grade inteira —, mas o custo dele ainda não se manifesta: com dois cartões não há o que varrer nem o que esquecer. O que o texto afirma do passo 5 é a estrutura, não a dificuldade; a dificuldade é projetada, e está declarada como projeção na seção 1.

E fora do sistema não há recurso nenhum, o que é diferente de haver um contorno. Não existe canal — dentro ou fora do produto — que responda "qual é o próximo tutorial deste assunto": nem lista, nem página, nem mensagem que alguém mande. A spec 0042 registrou que a dor que originou a Ajuda era projetada, e que nenhuma chamada de treinamento havia acontecido com ninguém; isso continua valendo, e por isso não se pode dizer que alguém hoje "pergunta a quem sabe". Não há a quem perguntar porque ainda não há quem pergunte.

Onde isso quebra, e é previsível sem esperar: o passo 2 é uma varredura, e varredura é uma operação que piora exatamente na proporção em que o material cresce — dobrar os tutoriais dobra o que se lê para achar um. O passo 5 é a mesma varredura de novo, agora com o custo extra de lembrar o que já foi assistido, coisa que a tela não registra.

Do lado de quem produz, a jornada existe e já foi percorrida duas vezes — é o /tutoriais, e é conferível no repositório:

  1. Escreve o roteiro em videos/<slug>.json, decidindo ali a que público serve.
  2. Grava, monta e o montar.mjs escreve o catálogo em apps/web/src/tutoriais/<slug>.json.
  3. Publica, e aí declara o grupo — em videos/publicacoes.json, como playlist do YouTube.
  4. O catálogo que a tela lê não recebe essa informação, e ninguém percebe, porque a tela não a usa.

  5. E quando quer ensinar o caminho do celular, não tem onde pôr. A skill grava no viewport que o config declara, e nada impede gravar em 375px — o que não existe é lugar para dizer que a gravação nova é a versão de celular daquele mesmo tutorial. As saídas hoje são duas, e as duas são ruins: criar um segundo tutorial com título quase igual, ou substituir o de desktop e perder um dos dois caminhos.

O passo 3 é onde a informação do assunto nasce e o passo 4 é onde ela se perde; o passo 5 é o ramo que não tem para onde ir. Foi assim com os dois tutoriais existentes: os dois têm grupo no YouTube, nenhum tem grupo no app, e os dois são de desktop sem que nada registre isso.

4. Resultado esperado

Como se sabe, depois de pronto, que resolveu:

  • Todo tutorial publicado tem um assunto declarado, e nenhum chega à tela sem ele.
  • O assunto de um tutorial é declarado uma vez só, e daí em diante a Ajuda e o canal dizem a mesma coisa sobre ele — sem que ninguém precise lembrar de repetir a informação no segundo lugar.
  • A Ajuda deixa de ser uma grade única: quem abre vê os tutoriais separados por assunto, e reconhece o seu conjunto sem varrer os dos outros.
  • De um tutorial se alcança outro do mesmo assunto sem passar pela lista inteira.
  • Quem abre a Ajuda vê o tutorial da interface que está usando, sem escolher nada — e quem precisa da outra chega nela.
  • Ensinar os dois caminhos não aumenta o tamanho da lista.
  • Nenhum dos dois tutoriais existentes precisa ser regravado para ganhar grupo.

5. Fora de escopo

  • Marcar o que já foi assistido. Exige guardar estado por usuário, e a tela hoje não guarda nada — é arquivo do bundle. É outra spec, e a decisão de tê-la não foi tomada.
  • Trilha com pré-requisito. O grupo organiza; não sugere ordem nem bloqueia. Decidido na entrevista.
  • Tutorial em mais de um grupo. Um grupo por tutorial. Decidido na entrevista.
  • Gravar as versões de celular de todos os tutoriais. Esta spec abre o lugar para elas; produzir o material é trabalho de /tutoriais, não de código, e acontece depois.

Com uma exceção deliberada: uma delas é gravada na entrega, e é o que torna o eixo do viewport verificável. Seis critérios de aceite exigem um tutorial com as duas gravações publicadas, e sem nenhuma versão de celular no ar eles seriam promessa. As alternativas são piores: um catálogo de mentira naquela pasta vai para o bundle de produção — o glob é ./*.json e não distingue teste de material —, e deixar seis critérios sem verificar entrega uma feature cuja metade principal ninguém exercitou. Grava-se a de cadastro-de-imobiliaria, que é o tutorial mais curto. - Escolher a versão manualmente como configuração de conta. Quem assiste não define preferência de interface num lugar e a carrega para sempre; a versão acompanha o aparelho em que ele está agora. - Busca ou filtro na Ajuda. O agrupamento é a resposta escolhida para achar; busca é outra resposta ao mesmo problema, e somar as duas antes de a primeira existir é desenhar no escuro. - Grupos administráveis pela interface. Os grupos são do produto, versionados no repositório junto do material que descrevem — não são dado de cliente. - Rever como a skill /tutoriais grava, narra e monta. Roteiro, síntese de voz, gravação, corte, vinhetas, legenda e miniatura ficam como estão; esta spec só alcança o que acontece com o assunto do tutorial. - Qualquer alteração na apps/api. O material didático não é dado de cliente e não passa por endpoint; criar um seria inventar superfície.

6. Revisão do problema

Rubrica: .claude/rubrics/problema.md — necessidade × jornada de hoje. Subagente de contexto limpo, novo a cada rodada.

Rodada Veredicto O que travou
1 REPROVADA Um Important: a spec não dizia por que o campo publico, que já existe, não serve de grupo — e nos dois tutoriais publicados ele coincide com a playlist. Mais três Minor: volume futuro afirmado sem lastro, mecanismo vazando no resultado esperado, e inferência de comportamento escrita como observação.
2 REPROVADA Um Important: a seção 1 dizia que o grupo "some quando o tutorial termina", e a seção 3 mostrava o contrário — ele fica gravado em videos/publicacoes.json. O argumento de urgência estava inflado.
3 REPROVADA Um Critical criado pela correção da rodada 2: ao fechar um Minor sobre a jornada externa, o texto afirmou um contorno humano ("perguntar a quem já sabe") como prática instalada e o atribuiu à spec 0042 — que registra o oposto, que nenhuma chamada de treinamento aconteceu com ninguém.
4 APROVADA Três Minor, triados abaixo.

Resultado: aprovada na rodada 4.

Sobre a rodada 3 não contar contra o limite de três. O achado dela citava uma linha que a rodada 2 escreveu, que é o sinal descrito no skill: correção que cria defeito é dívida de verificação, não desatenção. O que ela ensinou vale registrar, porque é o erro mais fácil de cometer neste portão — ao consertar um achado sobre jornada, a saída barata é inventar jornada. A rubrica existe justamente para isso, e pegou.

Triagem dos três Minor da rodada 4:

  • Corrigido — as frases que falavam em "classificar" e "reclassificação" prometiam um custo que o próprio documento retira dois parágrafos depois. A Parte II se apoiaria nelas.
  • Corrigido — "além do necessário" era fronteira circular: só se sabe o necessário depois de decidir a solução. Passou a nomear o que fica de fora.
  • Aceito como pendência — dois resultados esperados descrevem a forma ("separados por assunto", "declarado uma vez só") e não só o desfecho. Fica: é o pedido do usuário como ele o formulou, e enunciá-lo por elipse para evitar a aparência de solução tornaria o documento menos claro sobre o que foi combinado, não mais.

Parte II — A experiência

Não preencher antes da revisão da seção 6 estar aprovada.

7. Jornada pretendida

A palavra que vai à tela é "assunto". Este documento usa "assunto" e "grupo" como sinônimos ao explicar, mas o rótulo visível é sempre assunto — no cabeçalho da barra do desktop, no submenu do mobile e em qualquer texto de estado. "Grupo" e "tema" não aparecem para o usuário.

Ponto de entrada: o mesmo de hoje — o item "Ajuda", ancorado no rodapé do menu no desktop e no fim da gaveta no mobile. Nada muda aqui, e é deliberado: a Ajuda já é achável, e o que esta spec conserta acontece depois que a pessoa chegou.

Jornada, caso feliz:

  1. Ela abre a Ajuda e a primeira coisa que lê é a lista dos assuntos — Análise de crédito pela imobiliária, Gestão de imobiliárias —, com o primeiro deles já aberto, mostrando os tutoriais que contém. Nunca há uma tela em que todos os tutoriais de todos os assuntos apareçam juntos.
  2. Reconhece o seu sem ler o material dos outros: o nome do assunto basta para descartar o que não interessa agora.
  3. Abre o assunto que é o dele e vê os tutoriais dele — só os dele.
  4. Escolhe um. Assiste à gravação feita na interface que ela está usando — a de desktop se está no computador, a de celular se está no telefone — sem ter escolhido isso e sem saber que houve escolha.
  5. Ao terminar, segue para o próximo do mesmo assunto sem passar pela lista de todos os tutoriais. No desktop o assunto nunca saiu da tela: é um clique. No celular são dois — "Ver o assunto" e escolher —, porque ali o submenu cede a altura ao vídeo (7.2). Os dois caminhos são curtos; só não são do mesmo tamanho, e a spec diz qual é qual em vez de prometer um número que só vale numa das telas.

Custo de uso: nenhum campo novo a preencher, e nenhuma escolha de versão a fazer — o aparelho responde essa pergunta. A pessoa que assiste não digita nada nesta tela, e isso não muda. O que o sistema já sabe e portanto não pergunta: os papéis de quem entrou (usa para ordenar), qual tutorial está aberto (usa para marcar a posição no assunto), a que assunto ele pertence, e a largura da tela — que é como o app já distingue desktop de mobile em toda parte, e é a mesma régua aqui. Não há preferência a configurar, assunto a escolher nem versão a selecionar: a organização é do produto, e o aparelho responde pela versão.

O que a jornada de propósito não faz, e é a resposta à Parte I: ela não pede que a pessoa lembre o que já viu, não a manda buscar, e não a devolve à grade inteira entre um tutorial e outro. Os três eram o passo 5 da jornada de hoje.

7.1 Desktop

A Ajuda deixa de ser uma grade e passa a ser um navegador com duas áreas.

À direita, uma barra de assuntos. Ela lista os assuntos, um por linha. Clicar num assunto abre-o ali mesmo, revelando os títulos dos tutoriais dele logo abaixo, recuados — e clicar num título abre aquele tutorial na área principal, à esquerda.

À direita, e não à esquerda, apesar de o menu do app ficar à esquerda — e é justamente por isso: com o menu principal já ancorado de um lado, uma segunda barra vertical colada nele criaria duas colunas de navegação encostadas, e a pessoa passaria a ter de distinguir qual das duas responde "onde estou no produto" e qual responde "onde estou na Ajuda". Do lado oposto, cada uma guarda um sentido: à esquerda se anda pelo produto, à direita se anda pelo material. O assunto do tutorial aberto fica expandido, e o título dele, marcado; os demais ficam recolhidos, para a barra caber sem rolar enquanto os assuntos forem poucos.

O vocabulário é o do menu principal — seção, item, item ativo marcado —, e isso é deliberado: quem já usa o produto não aprende um segundo jeito de ler navegação. O que é novo é o abre-e-fecha: no menu do app a seção é rótulo fixo e os itens não recolhem. Aqui recolhem, porque lá as seções são cinco e conhecidas, e aqui os assuntos crescem com o material.

A gravação que toca é a de desktop, e abaixo do vídeo há uma linha discreta oferecendo a outra: "Este tutorial também tem versão para celular", com o caminho para ela. Acionada, ela troca a gravação no mesmo lugar — o vídeo passa a ser o da outra interface, o roteiro passa a ser o dela, a barra de assuntos não se mexe, e o endereço muda, para que aquele link leve de volta à mesma versão — substituindo a entrada no histórico, não empilhando uma nova. Empilhando, o botão voltar passaria a alternar versões antes de sair do tutorial, e a RN17c recusa exatamente esse tipo de poluição no histórico. Não sai da página e não perde o lugar. Não é um seletor em destaque — a pessoa já está vendo a versão certa para ela; o link existe para quem vai ensinar alguém que está no telefone, que é o caso real de quem administra a garantidora e atende a imobiliária por mensagem.

Clicar num assunto abre-o na barra e troca a área principal pelos cartões dele — inclusive quando há um tutorial aberto: sair do vídeo é o que a pessoa quis dizer ao clicar em outro assunto. O tutorial não fica pendurado atrás de um assunto que não é o dele; ficar seria a tela mostrando duas respostas para "onde estou".

Clicar de novo no assunto já aberto recolhe a lista e a área principal fica como está — não é navegação, é só arrumar a barra.

O topo da página acompanha o assunto escolhido: o PageHeader continua dizendo "Ajuda" como título, e o nome do assunto aparece como subtítulo. O título não vira o nome do assunto porque a pessoa não saiu da Ajuda — ela está dentro dela, e o topo deve dizer onde ela está, não o que ela clicou por último.

À esquerda, o conteúdo, ocupando a maior parte da largura. Ao entrar em /ajuda sem escolher nada, a área mostra os cartões do primeiro assunto — miniatura, título, frase e duração, como os cartões de hoje. Escolhido um tutorial, ela mostra o tutorial: o vídeo e o roteiro em passos, como hoje.

A barra não some quando o tutorial abre, e é aí que ela paga o próprio custo: terminado o vídeo, o próximo do mesmo assunto está visível na tela, sem voltar para lista nenhuma. É o passo 5 da jornada, e a resposta ao "de um tutorial não se chega ao seguinte" da Parte I.

E é aqui que três blocos disputam a largura, então quem cede está dito. Hoje a página do tutorial é de duas colunas — vídeo e roteiro — a partir de 1200px. Com a barra, passariam a ser três, e três não cabem: em 1280px sobrariam menos de 500px para o vídeo, que é o conteúdo. Então, com um tutorial aberto, o roteiro desce para baixo do vídeo e a tela volta a ter duas faixas: barra de assuntos e conteúdo. O roteiro em passos não perde nada com isso — ele é lido em sequência, e abaixo do vídeo é onde a leitura em sequência começa.

Abaixo de 1200px não há barra lateral: vale o submenu no topo do 7.2, e não uma terceira disposição intermediária — largura de tablet e de janela não maximizada, que ninguém lembra de conferir.

E a herança alcança a página do tutorial: entre 600 e 1200px o submenu também não acompanha o vídeo, então ali o próximo custa dois cliques, como no celular. Não é por altura — é por coerência: uma faixa de assuntos que aparece em 1100px e some em 1199px seria a tela mudando de ideia sem que nada tivesse mudado para quem olha.

Mas nem tudo o que o 7.2 decide por causa da altura de 375px vale aqui. Em 900px um cartão em 16:9 não come metade da tela, então os cartões ficam, com a frase de cada tutorial: entre 1200px e 900px o que muda é onde o navegador de assuntos aparece, não a forma de listar. A troca de cartão para lista de títulos acontece em 600px — o mesmo ponto em que a grade de hoje já deixa de ter colunas e vira empilhamento. Acima disso, cartões com frase; abaixo, lista de títulos. Reusar o ponto que a tela já usa evita inventar uma terceira largura para alguém conferir, e faz a mudança de forma coincidir com a mudança que já existe em vez de criar uma segunda a meio caminho.

A ordem dos assuntos, e o que é "o primeiro". É extensão da regra que já existe, não regra nova:

  • Um assunto é promovido se pelo menos um tutorial dele serve a algum papel de quem está olhando. Assunto não tem público — quem tem é o tutorial, e é a distinção da seção 1. Então a pergunta "este assunto é meu?" só se responde por dentro, e basta um casar. Um assunto que reúne tutoriais de públicos diferentes é promovido para qualquer um deles, e aparece inteiro — nunca recortado, pelo mesmo motivo que a lista não filtra por papel (spec 0042, RN7).
  • Entre os promovidos, e entre os demais, vale a ordem do catálogo — a mesma que a spec 0042 usa como desempate, e pelo mesmo motivo: qualquer outro critério seria inventado.
  • "O primeiro assunto" é o primeiro dessa ordenação — o que a tela abre por padrão no desktop, e a aba que vem marcada no mobile.

Com um único assunto — que é quase o caso de hoje, com dois assuntos de um tutorial cada — a barra mostra um item expandido e a tela funciona igual, sem estado especial.

Com muitos assuntos, a barra rola sozinha, como o menu do app já rola, e o assunto aberto é trazido para a vista quando a tela carrega direto num tutorial — senão quem chega por link vê uma barra rolada em outro ponto e não se localiza.

7.2 Mobile (375px)

O que no desktop é barra lateral, aqui é um submenu no topo. Uma faixa horizontal com os assuntos, rolável para o lado quando não couberem, com o assunto corrente marcado. O que o app já tem de mais próximo é a linha rolável de pílulas do MobileFilterBar — mesma mecânica de rolagem e de item marcado, outro propósito. Não é a barra de abas do rodapé, que é fixa, tem quatro lugares e pertence à navegação do app inteiro. Abaixo dela, a lista dos títulos daquele assunto, um por linha, com a duração ao lado; tocar num título abre o tutorial.

A escolha de lista, e não de cartões, é do viewport: em 375px um cartão com miniatura em 16:9 ocupa quase metade da altura útil, e três tutoriais viram três telas de rolagem. Uma lista de títulos mostra o assunto inteiro de uma vez, que é o que a pessoa veio ver.

A gravação que toca é a de celular, e a linha que oferece a outra também existe aqui — "também tem versão para computador" —, pelo motivo simétrico e mais raro: quem está no telefone consultando algo que vai fazer depois no computador. Ela fica logo abaixo do vídeo, antes do roteiro, e funciona como no desktop: troca a gravação no lugar, troca o roteiro junto, muda o endereço, e não tira a pessoa da página.

Aberto o tutorial, ele ocupa a tela — vídeo e roteiro empilhados, como hoje. O submenu de assuntos não fica por cima: em 375px a altura é o recurso escasso, e uma faixa fixa roubaria do vídeo. A volta é a ação secundária que a página já tem, hoje rotulada "Todos os tutoriais". Ela passa a devolver ao assunto de onde a pessoa saiu, e o rótulo passa a ser "Ver o assunto" — mandar para o assunto sob um rótulo que promete todos seria a tela mentindo sobre o próprio destino.

A trilha do topo acompanha. No desktop o PageHeader mostra "Ajuda" levando à raiz; passa a mostrar o nome do assunto, levando ao assunto. Dois caminhos de volta na mesma tela com destinos diferentes seria a tela discordando de si mesma.

A ação secundária existe nos dois viewports, e vale dizer o que muda em cada um. (A trilha, não: por docs/ui-patterns.md ela é só do desktop.) No desktop ela fica, apesar de a barra já dar o caminho: é a saída que está perto do olho de quem acabou de assistir, e tirá-la para economizar um botão custaria mais do que ela ocupa. Em 375px o PageHeader a renderiza só como ícone contornado, com o rótulo virando nome acessível — então ali o rótulo não é lido por quem enxerga, e o que impede a tela de enganar é o destino ser o mesmo de onde a pessoa veio, não a palavra.

Quem chega direto na URL do tutorial — link compartilhado, recarregamento, atalho do PWA — não tem assunto de onde saiu. Nesse caso a volta leva ao assunto do tutorial aberto, que é para onde ela levaria de qualquer forma se a pessoa tivesse chegado navegando. Não há caso em que a volta fique sem destino.

Com um assunto só, a faixa mostra uma pílula marcada e não rola — ela fica, em vez de sumir: some-la faria a tela do celular perder a única indicação de que existe organização por assunto, e a primeira vez que surgisse um segundo assunto a navegação apareceria do nada.

O assunto marcado é trazido à vista quando a faixa rola — ao chegar por link, ao recarregar, ao voltar de um tutorial. É a mesma correção que o desktop faz na barra, e pelo mesmo motivo: uma faixa rolada em outro ponto, com a marca fora da tela, faz a pessoa concluir que nenhum assunto está selecionado.

Alvo de toque de 44px nas abas e nas linhas da lista — a medida que o app já usa (specs 0011 e 0016). Título truncado em uma linha; em 375px não cabe frase, e cortar é melhor que empurrar a duração para fora.

O que não aparece no celular: a frase de cada tutorial, que no desktop fica no cartão. É o que se sacrifica para o assunto inteiro caber numa tela, e ela reaparece na página do tutorial, abaixo do título.

PWA: a Ajuda é tela como as outras e entra no app instalado. Quem já a tinha instalada vê o navegador depois que o service worker atualiza o bundle — comportamento de toda tela, não específico desta.

O link ganha. Quem manda o endereço de uma versão específica escolheu aquela versão — em geral para ensinar alguém que está na outra interface, que é o caso citado em 7.1. Fazer o aparelho sobrepor a escolha transformaria "olha este vídeo" em "olha um vídeo qualquer sobre isto".

Então, aberto no celular um endereço da versão de desktop, toca a de desktop, e a linha de descompasso aparece — "Esta gravação foi feita no computador" —, junto do caminho para a versão de celular. As duas informações juntas dizem tudo o que a pessoa precisa: por que a tela do vídeo não parece a dela, e como ver a que parece.

Sem versão declarada no endereço, o aparelho decide, que é o caminho normal da jornada: quem entra pela Ajuda não escolhe versão e não sabe que houve escolha.

7.4 O assunto tem endereço

Escolher um assunto muda a URL, e isso não é detalhe técnico adiantado: é o que decide três comportamentos que a pessoa percebe.

  • O botão voltar do navegador — e o gesto de voltar do PWA — devolvem ao assunto anterior, não à raiz da Ajuda. Sem endereço próprio, voltar depois de percorrer três assuntos sairia da Ajuda de uma vez, que é o tipo de coisa que faz alguém desistir de explorar.
  • Um assunto é compartilhável. Mandar "o material de análise de crédito está aqui" é mandar um link, não uma instrução de onde clicar depois de chegar.
  • Recarregar a página mantém o assunto aberto, em vez de devolver ao primeiro.

7.5 Decisão em aberto: ordem dentro do assunto

Fechada por ora sem anterior/próximo. O navegador e a volta ao assunto resolvem a jornada nos dois viewports — um clique no desktop, dois no celular, como o passo 5 diz. Acrescentar anterior/próximo depois é barato; gravar uma ordem errada em todo tutorial, não. O texto abaixo fica registrado para quando a decisão for tomada.

Fica registrada, e não suposta. O navegador acima mostra os tutoriais de um assunto em lista, e uma lista tem ordem. Duas perguntas seguem sem resposta, e as duas são de produto:

  • De onde vem a ordem? A seção 5 fechou "trilha com pré-requisito" fora do escopo, e a entrevista decidiu que o grupo organiza e não sugere sequência. Mas ordem de exibição não é pré-requisito: sem uma declarada, a lista sai em ordem de nome de arquivo, e "cadastro" vem antes de "parâmetros" porque C vem antes de P.
  • A página do tutorial ganha "anterior" e "próximo"? Foram pedidos na entrevista, e fazem sentido quando há ordem. Com o navegador sempre visível no desktop, eles passam a ser um segundo caminho para o mesmo lugar — e no mobile, onde o submenu não acompanha o tutorial, eles são o único caminho para o vizinho sem voltar.

Enquanto não houver decisão, esta spec não especifica anterior/próximo, e a ordem dentro do assunto é a do catálogo. As duas coisas mudam juntas e não devem ser implementadas por suposição.

7.6 Quem produz: onde o assunto é declarado

A seção 2 diz que quem produz sente antes de todo mundo, e ele tem superfície nesta spec — não é só construção. A jornada dele hoje tem quatro passos (seção 3), e o assunto nasce no passo 3, ao publicar. O que muda:

  • O assunto passa a ser declarado no mesmo lugar em que já se declara, junto dos demais dados de publicação do tutorial, e daí em diante vale para a Ajuda e para a playlist do canal, sem ser repetido. É o segundo resultado esperado da seção 4.
  • Esquecer de declarar não passa em silêncio. Ao rodar a verificação do repositório, quem produz lê uma frase que nomeia o tutorial e o campo que falta — não um código nem um caminho de arquivo solto. É a mesma forma das verificações que já existem para o par roteiro/catálogo.
  • Declarar um assunto que não existe também não passa. A mensagem diz qual foi declarado e quais existem, para a correção ser escolher da lista em vez de adivinhar.

O que ele não ganha: tela. Não há administração de assuntos pela interface — a seção 5 fechou isso, e o motivo é que assunto é do produto, versionado junto do material que descreve, não dado de cliente.

7.7 Estados

  • Vazio (nenhum tutorial publicado) — o que a tela já faz hoje: "Nenhum tutorial publicado ainda", com a linha explicando que aparecem assim que o primeiro subir. Sem navegador, nem barra nem submenu: assunto sem conteúdo é rótulo sobre nada, e uma barra vazia ao lado de um aviso de vazio diz duas vezes a mesma coisa.
  • Carregando — não existe, e é verdade e não descuido: o catálogo é arquivo do bundle, não resposta de API. Quando a tela pinta, os dados já estão lá. Não há esqueleto porque não há espera.
  • Erro — pelo mesmo motivo, não há erro de rede a mostrar na lista. O erro que existe é na página do tutorial, quando o player não carrega, e ele já está resolvido hoje: aviso com o link direto para o vídeo, e o roteiro seguindo legível abaixo. No desktop a barra de assuntos continua ali — ela vem do catálogo, não do YouTube, e é justamente quando o vídeo falha que ter para onde ir importa mais. Em 375px não há submenu na página do tutorial (7.2), então a saída é a mesma de sempre: "Ver o assunto".
  • Sem permissão — não existe para a Ajuda: todo autenticado vê todos os tutoriais, e a spec 0042 decidiu não criar permissão para isto. O que existe é a ação "Ir para a tela" desabilitada com a glosa, quando a pessoa não pode fazer o que o tutorial ensina — e isso não muda. Um tutorial cuja tela ela não alcança continua aparecendo no grupo: aprender o que o outro lado faz é o motivo de a lista não filtrar por papel.
  • Assunto sem tutorial publicado — acontece o tempo todo: youtubeId: null é o estado de um tutorial gravado cujo vídeo ainda não subiu, e o catálogo já filtra por ele. O assunto não aparece — nem na barra do desktop, nem na faixa do mobile. Assunto é organização do que existe; um assunto vazio é uma gaveta que promete e não entrega, e se ele fosse o primeiro a tela abriria no nada. Some sem aviso, porque para quem assiste ele nunca existiu.
  • Assunto inexistente na URL — o endereço do assunto é compartilhável e sobrevive a recarregamento (7.4), então também sobrevive a um assunto renomeado ou fundido. A tela abre o primeiro assunto, com uma linha acima do conteúdo: "O assunto que você procurava não existe mais — estes são os que existem". Não é erro de tela cheia: o que a pessoa queria era material de ajuda, e é material de ajuda que ela está vendo. Vale nos dois viewports; no mobile a linha fica sob o submenu.
  • Tutorial inexistente na URL — o que já acontece: "Este tutorial não está mais disponível", com o caminho de volta. O navegador aparece: no desktop a barra fica, com o primeiro assunto aberto; no mobile o submenu volta, já que não há tutorial ocupando a tela. Um aviso de "não existe" ao lado dos assuntos que existem é melhor que um aviso sozinho — a saída fica à vista em vez de depender de um botão.
  • Tutorial com uma versão só — que é o estado dos dois que existem hoje, ambos de desktop. A pessoa vê a versão que existe, seja qual for o aparelho dela. Quando a gravação não corresponde ao aparelho, uma linha diz qual é: "Esta gravação foi feita no computador". Correspondendo, nada é dito — anunciar o óbvio a quem já está vendo a versão certa gasta atenção e ensina que a tela fala por falar. Não se esconde o tutorial de quem está no celular por falta da versão dele — material da interface errada ensina menos, mas ensina; nenhum material não ensina nada. E não se finge que é a versão dele: sem a linha, quem assiste no celular um vídeo gravado no desktop conclui que errou o caminho, não que está vendo outra interface.
  • Tutorial publicado sem assunto declarado — é o estado dos dois catálogos que existem hoje, então não é hipótese. Ele aparece, num assunto de sobra com rótulo próprio ("Outros tutoriais"), sempre ao final — é a única exceção à ordenação por papel, e deliberada: promover uma gaveta de sobras porque um dos itens dela casa com o papel de quem olha daria destaque justamente ao que ainda não foi organizado. Some-lo seria pior: tirar da tela um tutorial que está publicado por causa de um campo em branco é castigar quem assiste por um erro de quem produz. O grupo de sobra só existe quando há algum tutorial nele.
  • Assunto declarado que não corresponde a grupo conhecido — mesmo tratamento do anterior: aparece, sob o título do assunto tal como declarado, em vez de sumir. Como as duas situações passam a ser impedidas antes de chegar à tela (Parte III), o que se descreve aqui é a rede de segurança, não o caminho normal.

Os dois tutoriais que existem hoje não chegam a usá-la: eles recebem o assunto na entrega desta spec — é transporte de um campo que já está em videos/publicacoes.json, não decisão nova —, e nascem organizados junto com a tela que os organiza.

8. Revisão da experiência

Rubrica: .claude/rubrics/experiencia.md — jornada × viewports. Subagente de contexto limpo, novo a cada rodada.

Rodada Veredicto O que travou
1 REPROVADA Quatro Important: "anterior/próximo" pressupunha uma ordem que a seção 5 negava; a promoção de grupo usava "papel do grupo", que não existe; faltava dizer onde o par cai quando a página empilha; e faltava o estado do tutorial sem assunto.
2 REPROVADA Três Important, depois do redesenho para barra lateral e submenu: o clique no assunto não dizia o que fazia com a área de conteúdo, a ordem entre assuntos tinha sumido na reescrita, e o controle da volta não estava nomeado.
3 REPROVADA Quatro Important: a jornada prometia um toque no mobile e o 7.2 negava; o rótulo da volta era promessa sem valor; a barra permanente criava uma terceira coluna sem dizer quem cede; e quem produz não tinha jornada nem estado.
4 REPROVADA Dois Important, depois de entrar o eixo do viewport: o passo 5 seguia só do desktop, e o endereço de assunto inexistente — criado pela própria seção 7.4 — não tinha estado.
5 REPROVADA Três Important: assunto conhecido sem tutorial publicado; conflito entre o endereço que fixa a versão e o aparelho que a escolhe; e a fronteira de forma de listagem entre 600 e 900px.
6 APROVADA Sete Minor, triados abaixo.

Resultado: aprovada na rodada 6.

Seis rodadas estouram o limite de três, e isso está registrado em vez de escondido. O laço não emperrou — cada rodada trouxe achados novos e mais estreitos que a anterior, de "não se diz o que o clique faz" até "falta a fronteira entre 600 e 900px" —, mas convergiu devagar, e o motivo é conhecido: o escopo mudou duas vezes no meio. Na rodada 2 o navegador deixou de ser grades empilhadas e virou barra lateral e submenu; na rodada 4 entrou o eixo do viewport, que obrigou a reescrever a Parte I e metade da Parte II. As duas mudanças vieram de quem pediu a spec e melhoraram o desenho; as duas custaram rodadas. Fica como registro do que este portão mede: ele mede o texto, e texto reescrito é texto novo.

Depois da rodada 5 o laço parou e a decisão de seguir foi levada a quem pediu a spec, como o skill manda quando o limite estoura.

Depois da aprovação, uma mudança não revalidada. A barra de assuntos passou da esquerda para a direita, a pedido de quem pediu a spec, já com a Parte II aprovada. O portão não rodou de novo sobre ela; o que foi feito no lugar foi percorrer por escrito os estados que a mudança toca — o roteiro descendo para baixo do vídeo, a barra permanecendo com o tutorial aberto, a rolagem dela, o sumiço abaixo de 1200px e o aviso de assunto inexistente ao lado dela —, e nenhum deles dependia do lado. Fica registrado como verificação de menor força que uma rodada, e não como rodada.

Pendência conhecida e não fechada: a barra não diz o que faz com nome de assunto comprido. O mobile trunca em uma linha; o desktop não decidiu, e com a barra à direita ela divide largura com o conteúdo em vez de com o menu.

Triagem dos sete Minor da rodada 6:

  • Corrigidos, seis — a herança do 7.2 entre 600 e 1200px alcançando a página do tutorial; o antecedente ambíguo de "ela existe nos dois viewports"; o que acontece com os dois tutoriais de hoje na entrega; a mecânica da troca de versão no mobile; a faixa com um assunto só; e o que o topo da lista mostra com um assunto escolhido. Todos custavam uma frase, e todos virariam pergunta na implementação.
  • Aceito como pendência, um — "Outros tutoriais" ficar sempre ao final é exceção à ordenação por papel, e o texto passou a nomeá-la como exceção em vez de deixá-la implícita. Continua sendo uma irregularidade na regra; é deliberada, e está escrita.

Parte III — Detalhamento

Não preencher antes das revisões das seções 6 e 8 estarem aprovadas.

9. Regras de negócio

O assunto, e onde ele é declarado

  • RN1. O assunto de um tutorial é declarado uma vez só, no bloco publicacao do roteiro (videos/<slug>.json) — que é onde o produtor já declara playlist, título e descrição. Daí ele viaja para os dois consumidores sem ser redigitado: o catálogo do app e a playlist do canal.
  • RN2. A chave publicacao.playlist passa a se chamar assunto, e é o mesmo dado. O nome muda porque o significado mudou: era "em que playlist do YouTube isto entra" e passa a ser "de que assunto isto trata", com a playlist virando consequência. Renomear é barato agora — dois roteiros — e evita que o app leia para sempre um campo cujo nome fala de outro produto.
  • RN3. Os assuntos existentes são declarados em videos/publicacoes.json, na chave playlists, que passa a se chamar assuntos. Cada um tem titulo (o que a tela mostra), descricao e visibilidade. Os campos de ligação com o canal — youtubeId, url, criadaEm — passam a ser por interface, porque cada assunto tem duas playlists (RN7b):
"gestao-imobiliarias": {
  "titulo": "Gestão de imobiliárias",
  "descricao": "...",
  "visibilidade": "publica",
  "playlists": {
    "desktop": { "youtubeId": "PLaMoIXO0Bgzg", "url": "...", "criadaEm": "2026-08-22" },
    "mobile":  null
  }
}

null quer dizer "ainda não existe", que é o estado de toda playlist de celular hoje. As duas que já existem no canal migram para a chave desktop, porque é o que elas contêm. A chave do objeto é o identificador usado pelos roteiros.

Os identificadores não mudam, e o mapeamento fica escrito porque eles viram segmento de URL compartilhável (7.4): imobiliarias passa a ter o título "Análise de crédito pela imobiliária", e gestao-imobiliarias, "Gestão de imobiliárias". O primeiro nomeia público e o título nomeia assunto — herança da criação; mudar identificador quebraria link já compartilhado por um ganho cosmético. - RN3a. A entrada tutoriais sai de playlists — a playlist guarda-chuva, não o bloco tutoriais do topo do arquivo, que é o registro de publicação e permanece. Ela é a playlist guarda-chuva criada antes de existirem assuntos, e não nomeia assunto nenhum — hoje está vazia, porque os dois tutoriais migraram para as listas por público. Renomear o mapa sem tirá-la a tornaria um assunto declarável e aceito pela verificação, e a Ajuda passaria a mostrar uma gaveta chamada "Tutoriais" dentro da tela de tutoriais. A playlist continua existindo no YouTube; o que sai é a declaração dela como assunto. - RN3b. O titulo do assunto é um campo só, e serve à tela e à playlist. É o que a Ajuda exibe na barra e no submenu, e é o que o publicar-youtube.mjs usa para criar e renomear a playlist no canal — não há um título de tela e outro de YouTube.

E isto exige mudar o comando, não só o dado. Hoje o playlists do publicar-youtube.mjs só cria o que falta — playlist que já tem youtubeId é pulada, e não há atualização de título em lugar nenhum. Entregue sem isso, a Ajuda passaria a exibir os títulos novos enquanto o canal seguiria com os antigos, sem nada acusar: a mesma duplicação sem verificação que esta RN existe para impedir, agora entre o app e o YouTube. O comando passa a atualizar snippet.title e snippet.description da playlist já criada quando divergirem do declarado.

Consequência a assumir, e ela é visível de fora: os títulos passam a ser os que a Parte II mostra — Análise de crédito pela imobiliária e Gestão de imobiliárias —, e as playlists existentes são renomeadas na primeira sincronização depois da entrega — e como cada assunto passa a ter uma lista por interface (RN7b), o título de cada uma é o do assunto mais o sufixo: "Análise de crédito pela imobiliária — no computador". As atuais ("Tutorial Imobiliárias Parceiras", "Tutoriais Gestão de Imobiliárias") têm dois dias e descrevem público; o agrupamento desta spec é por assunto, e repetir "tutorial" no nome de cada lista dentro de um canal chamado Tutoriais é redundância que só se percebe quando as listas se multiplicam.

Dois campos foram cogitados e recusados: manter um título de tela e outro de playlist é criar a segunda coisa a manter em dia, que é a classe de dívida que esta spec fecha.

É o único ponto desta spec sem caminho de volta automático. Reverter o deploy devolve catálogo, arquivos e publicacoes.json; os títulos das playlists ficam como ficaram, e desfazer é ato manual no YouTube Studio. Vale saber antes, não depois. - RN4. O montar.mjs escreve no catálogo do app um objeto assunto, e este é o esquema, citado por todas as regras que o tocam (RN5b, RN13, seção 11, verificação 5 do preflight):

"assunto": {
  "id": "gestao-imobiliarias",        // identificador: URL e comparação
  "titulo": "Gestão de imobiliárias", // o que a tela exibe
  "ordem": 2,                         // posição na declaração de `assuntos`
  "sintetico": false                  // sempre presente; true nos dois casos da RN5b
}

sintetico é obrigatório e sempre escrito, não opcional. Opcional, ele sairia da tipagem sem ninguém notar — o preflight tolera chave a mais, então o portão não pegaria —, e a RN13 viraria a adivinhação que a RN5b existe para impedir.

Os três vão para o catálogo porque a tela não pode ler videos/publicacoes.json, que está fora do bundle por decisão da spec 0042. A ordem é o menos óbvio e o mais necessário: sem ela a RN13 seria impossível de cumprir, porque o app monta a lista de assuntos a partir dos catálogos, que o import.meta.glob entrega em ordem alfabética de arquivo — e a ordem que sairia seria a de primeiro aparecimento por slug, não a declarada. - RN4a. A chave tutoriais.<slug>.playlist de videos/publicacoes.json deixa de existir. É a segunda declaração do mesmo assunto — o registro de publicação a mantinha para saber em que playlist pôr o vídeo —, e enquanto ela existir a RN1 é falsa. Quem precisa do assunto passa a lê-lo no roteiro, que é onde ele é declarado. - RN5. Um tutorial pertence a exatamente um assunto. Declarar dois é erro, não recurso. - RN5a. Identificador de assunto e slug de tutorial não podem coincidir, e o preflight verifica. Sem isso, /ajuda/<segmento> ficaria ambíguo (RN18) e a tela teria de adivinhar. - RN5b. assunto nunca é nulo no catálogo, e é o montar.mjs que garante — ele roda antes do portão, então "a RN6 impede" não o socorre. Sem assunto no roteiro, ele escreve { id: "outros", titulo: "Outros tutoriais", ordem: 9999, sintetico: true }; com assunto que não existe em assuntos, escreve o identificador declarado, titulo igual ao próprio identificador, ordem: 9998 e sintetico: true.

O campo sintetico existe porque sem ele a tela não tem como saber. O outros é reconhecível pelo identificador reservado; o assunto declarado-e-inexistente chega ao bundle idêntico a um legítimo, e a RN13 manda tratá-lo diferente. Sem marcador, quem implementa inventaria um (ordem >= 9998? titulo === id?) — e a saída fácil, não distinguir, promove por papel exatamente a gaveta que a regra manda deixar por último. Entre dois desconhecidos, desempata o id em ordem alfabética. As duas são situações de transição que o portão recusa em seguida — o que a tela recebe é material organizado o suficiente para aparecer, e o que o produtor recebe é uma reprovação que nomeia o problema. - RN5c. O identificador do assunto vira segmento de URL, então tem forma exigida: minúsculas, dígitos e hífen, sem espaço e sem acento. Os dois de hoje já são assim por hábito; o preflight passa a exigir por regra, porque um assunto chamado "Gestão & Imobiliárias" produziria um endereço que ninguém consegue mandar por mensagem. - RN6. Tutorial sem assunto declarado, ou com assunto que não existe em assuntos, não passa no make preflight. A mensagem nomeia o tutorial, o campo e — no segundo caso — os assuntos que existem, para a correção ser escolher da lista.

As duas gravações

  • RN7. Um tutorial tem de uma a duas gravações, identificadas por viewport: desktop e mobile. São gravações do mesmo tutorial — mesmo slug, mesmo título, mesma frase, mesmo assunto, mesmo destino — ensinando o mesmo processo em interfaces diferentes.
  • RN7a. A identidade de uma versão publicada passa a ser (slug, viewport), e não mais o slug sozinho. O registro de publicação guarda uma cadeia de versões por gravação: cada item de versoes ganha viewport, e "a versão ativa" é a ativa daquele viewport.

Sem isto, ganhar uma segunda gravação tira a primeira do ar. O publicacoes.mjs publicar acha o item ATIVO do tutorial e o marca A_REMOVER apontando para o novo; o publicar-youtube.mjs aposentar então torna privado e tira da playlist tudo que estiver A_REMOVER. Publicar a gravação de celular do analise-credito-imobiliaria aposentaria o vídeo de desktop que está em produção — um efeito colateral de acrescentar material, que é o oposto do que esta spec quer.

Os dois comandos passam a receber o viewport: publicar <slug> <youtubeId> --viewport mobile, e o aposentar compara dentro da mesma cadeia.

Item de versoes sem viewport lê-se como desktop, pela mesma razão da RN10 — é o que as versões já registradas são. Sem esse default, regravar o desktop depois desta entrega procuraria o ATIVO da cadeia desktop, não acharia os itens legados, não aposentaria nada, e a playlist voltaria a acumular cópias do mesmo tutorial sem erro em lugar nenhum. O registro é migrado na entrega — carimbar viewport: "desktop" nos itens existentes —, e o default fica como rede para o que escapar. - RN7b. Um assunto na Ajuda, duas playlists no canal — uma por interface. Dentro do app a versão é escolhida pelo aparelho e o assunto é um só; no YouTube não há aparelho a consultar, e quem chega direto ao canal escolhe olhando. Duas gravações do mesmo processo na mesma lista fariam essa pessoa abrir a errada e concluir que o produto é outro.

Cada assunto tem, portanto, duas playlists, tituladas com o do assunto mais o sufixo da interface: "Análise de crédito pela imobiliária — no computador" e "— no celular". A gravação de cada viewport entra na playlist daquele viewport, e só nela.

A descrição das duas é a mesma do assunto, mais uma linha dizendo a interface — quem chega numa lista pela busca do YouTube não viu o título completo, e a descrição é onde isso se confirma.

A reconciliação passa a ser por gravação. Hoje o comando playlists acha a versão ativa do tutorial e decide a partir dela o que entra e o que sai de cada lista; com duas ativas, a de celular nunca seria conferida — e se o envio falhasse em pô-la na lista, nada a recolocaria. É a mesma classe de defeito que a RN21c da 0042 criou para impedir.

A playlist de uma interface só nasce quando existe gravação dela. Uma lista vazia chamada "— no celular" prometeria material que não existe, que é o oposto do que a separação quer. - RN7c. O vídeo de celular sobe com sufixo no título: "Abrir uma análise de crédito — no celular — Garantora". A descrição é a do canônico, salvo quando o roteiro de celular declara a sua — o que ele pode fazer, e precisa fazer quando há capítulos (RN9b).

Sem o sufixo, as duas gravações subiriam com título idêntico e ficariam lado a lado na mesma playlist — que é literalmente o estado que a RN21c da 0042 descreve como defeito, quando a lista acumulou quatro cópias indistinguíveis. Dentro do app a distinção é automática (a régua do aparelho); no canal, quem vê a lista precisa de texto.

O sufixo entra antes da cauda da marca, não no fim. O título declarado é "Abrir uma análise de crédito — Garantora"; concatenar produziria "… — Garantora — no celular", que soa como se a marca tivesse mudado. A regra: havendo " — Garantora" no fim, insere-se antes dela; não havendo, acrescenta-se ao fim. O preparar do publicacoes.mjs passa a receber --viewport e compõe assim. O sufixo continua valendo mesmo com as playlists separadas: o vídeo aparece também em busca e em sugestões, fora de qualquer lista, e ali o título é tudo o que distingue. - RN8. Cada gravação tem o que hoje é do tutorial e na verdade é dela: youtubeId, duracaoSegundos, miniatura e roteiro (as falas com marca de tempo). São diferentes entre as duas: os passos do celular não são os do desktop, então as falas e as durações também não são. - RN8a. O nome do arquivo ganha o viewport, e sem isso a RN15a é falsa por construção: hoje a miniatura sai em <miniaturas>/<slug>.png e o vídeo em <saida>/<slug>/<slug>.mp4, então montar a gravação de celular sobrescreveria a imagem da de desktop e as duas entradas do catálogo apontariam para o mesmo arquivo — com o portão verde, porque a verificação só confere que o arquivo existe. Passam a ser <slug>-<viewport>.png, <slug>-<viewport>.mp4 e <slug>-<viewport>.srt — a faixa de legenda pelo mesmo motivo e com o mesmo mecanismo de sobrescrita.

E a colisão não é só dos três arquivos finais. narrar.mjs e gravar.mjs escrevem narracao.json, gravacao.json, audio/ e video/ em <saida>/<slug>/, e o montar.mjs lê os dois de lá e usa <slug>/montagem como pasta de trabalho — tudo chaveado pelo slug, que a RN9 mantém idêntico entre as duas gravações. Narrar a versão de celular sobrescreveria os intermediários da de desktop, e remontar a de desktop depois exigiria regravá-la.

A pasta de saída passa a ser <saida>/<slug>/<viewport>/. Para o .mp4 e o .srt o sufixo no nome vira conveniência — é o nome que sobe ao YouTube e circula fora da pasta. Para a miniatura o sufixo é obrigatório: ela não mora em <saida>, e sim em apps/web/public/tutoriais/, que é versionada e é de onde a tela a serve — a pasta por viewport não a alcança. Os arquivos de hoje são renomeados na entrega, junto com a regeneração dos catálogos. - RN9. O roteiro de produção de cada gravação é um arquivo próprio — videos/<slug>.json e videos/<slug>.mobile.json —, porque os passos e a narração diferem. O que os liga é o slug; o que os distingue é a chave viewport. - RN9a. A montagem funde, não sobrescreve. O montar.mjs hoje monta o catálogo do zero e grava o arquivo inteiro; com duas gravações, montar a de desktop depois da de celular apagaria a de celular — e o portão ficaria verde, porque nada exige que ela exista. Ele passa a ler o catálogo existente e substituir apenas a gravação do viewport que está montando, preservando a outra.

Os campos de topo vêm sempre de videos/<slug>.json, o roteiro canônico (RN9b) — e não do catálogo anterior. Preservá-los do catálogo manteria um título velho depois de o roteiro mudar, e o portão reprovaria por divergência; lê-los do canônico faz montar qualquer uma das duas gravações deixar o topo correto. É também o que permite montar a de celular primeiro, com o catálogo ainda inexistente: o .mobile.json não declara esses campos, mas o canônico está lá do lado.

Pelo mesmo motivo, --so-catalogo, --youtube-id e --despublicar passam a exigir --viewport: sem ele, não há como saber a qual gravação o identificador se refere. - RN9b. O roteiro de celular declara só o que difere, e o de desktop é o canônico. videos/<slug>.mobile.json tem slug, viewport: "mobile", passos e o que a gravação dele exigir (setup, rota, espera, desfazer).

publicacao.descricao é a exceção que ele pode declarar, e por um motivo concreto: a descrição carrega os capítulos com marca de tempo, e os do celular não são os do desktop — os passos diferem, então as marcas diferem. Herdar a descrição do canônico publicaria o vídeo de celular com os capítulos do outro, apontando para instantes que não existem nele. Declarada, vale a dele; ausente, vale a do canônico, que é o caso de um tutorial cuja descrição não tem capítulo.

As demais chaves são do tutorial e ele não pode redeclarar — esta é a lista fechada, citada pela verificação 9 e pelo critério correspondente: titulo, frase, publico, destino e destinoExige. São do tutorial, não da gravação, e declará-las duas vezes recriaria a duplicação sem verificação que a Parte I aponta como o jeito de sair pior. Onde faltarem, valem as do videos/<slug>.json.

publicacao é o caso de meio-termo, e a regra precisa ser exata: ou está ausente, ou está presente contendo apenas descricao. Qualquer outra chave dentro dela — titulo, visibilidade, assunto, paraCriancas — é do tutorial e reprova. É o que a verificação 9 confere, e é o que impede a exceção dos capítulos de virar uma porta aberta para redeclarar o assunto por dentro.

papel é a exceção, e vai ao contrário: é obrigatório nos dois. Ele diz quem aparece na tela durante a gravação, e o gravar.mjs o lê do roteiro que está executando — não há fusão entre roteiros, cada um é lido sozinho. Omiti-lo no .mobile.json faria a gravação cair no papelPadrao do config e o vídeo de celular sair logado como outro perfil, sem erro em lugar nenhum. O preflight recusa quando os dois divergem: dois vídeos do mesmo tutorial com protagonistas diferentes seriam material ensinando duas coisas.

  • RN9c. O viewport declarado no roteiro decide a largura da gravação, e não o contrário. Hoje o gravar.mjs lê a largura só do config.json e a carimba no artefato; com a chave no roteiro, ele passa a usá-la: desktop grava na largura do config, mobile grava em 375px. E a montagem recusa se a largura carimbada no artefato não corresponder ao viewport declarado. A chave tem dois formatos em arquivos diferentes, e é bom saber qual é qual: no roteiro e no catálogo, viewport é "desktop" ou "mobile"; no artefato de gravação, é o par {width, height} que o gravar.mjs já carimba. A RN9c compara um com o outro, e não os confunde. A correspondência reusa o corte de 600px da RN15, em vez de exigir um número exato: gravação mobile tem de ter sido feita abaixo de 600px, e desktop, de 600px para cima. Exigir largura exata amarraria a spec ao config.json, que não é versionado; o corte responde à pergunta que importa — em que interface aquilo foi gravado.

Sem isso, nada impede uma gravação rotulada mobile no catálogo e feita em 1280×720, servida a quem está em 375px — e o config.json nem é versionado, então ninguém revisaria o descompasso num diff. - RN10. viewport ausente no roteiro significa desktop. É o que os dois roteiros existentes são, e é o padrão do config.json (1280×720). Sem esse default, esta spec quebraria o material que já existe para ganhar um campo que ele já responde por omissão. - RN11. Uma gravação sem youtubeId (produzida, não publicada) não conta — nem aparece na Ajuda, nem conta para a exigência de haver pelo menos uma publicada. Ela continua existindo no catálogo, e o preflight continua aceitando — "produzido, não publicado" é estado legítimo e é o que o montar.mjs produz antes de qualquer publicação. Um tutorial cujas duas gravações estão sem identificador não aparece na Ajuda, como já acontece hoje (RN3 da spec 0042).

A tela

  • RN11a. Os dois pontos de corte do layout são regra, não prosa: a barra lateral existe a partir de 1200px — abaixo disso, submenu no topo —, e a listagem troca cartão por lista de títulos abaixo de 600px. Os dois reusam pontos que o app já tem: 1200px é onde a página do tutorial vira duas colunas hoje, e 600px é onde a grade da Ajuda já deixa de ter colunas. Nenhum dos dois é a régua da gravação, que é o useIsMobile da RN15 — são perguntas diferentes: como a página se arruma, e qual interface a pessoa está usando.
  • RN12. A Ajuda mostra os assuntos que têm pelo menos um tutorial publicado. Assunto sem tutorial publicado não aparece (7.7).
  • RN13. A ordem dos assuntos: primeiro os que têm pelo menos um tutorial servindo a algum papel de quem olha; depois os demais. Entre iguais, a ordem de declaração em assuntos. Os assuntos sintéticos da RN5b — as duas espécies, e quantos houver de cada — são sempre os últimos, e não participam da promoção por papel — nem a gaveta outros, nem o assunto declarado que não existe. São estados de transição que o portão recusa; promovê-los daria destaque justamente ao que ainda não foi organizado. Ele tem identificador reservado outros e título "Outros tutoriais". O preflight recusa outros como chave de assuntos e como slug de tutorial — sem essa verificação, declarar outros em assuntos passaria a ser aceito e a gaveta de sobra colidiria com um assunto de verdade. /ajuda/outros resolve como qualquer assunto — tem endereço, sobrevive a recarregamento e é compartilhável —, e deixa de resolver quando a gaveta esvazia, caindo no comportamento de assunto inexistente (7.7). Ele existe só enquanto houver tutorial sem assunto — o que, com a RN6 em vigor, é situação de transição.
  • RN14. Dentro do assunto, a ordem é a do catálogo — que é a alfabética por nome de arquivo, como o import.meta.glob entrega. É arbitrária, e é por causa dela que a ordem do assunto precisou existir (RN4); dentro do assunto ela fica assim porque a alternativa exigiria a decisão que a 7.5 deixou em aberto. Não há ordem declarada por tutorial — é a decisão registrada em aberto na 7.5.
  • RN14a. A linha da lista em 375px tem título e duração, e não miniatura. "Lista de títulos" (RN11a) é o oposto de cartão: o que sai é a miniatura e a frase, e o que fica é o que identifica e o que mede o compromisso. A miniatura da RN15a governa o cartão, que é o que aparece de 600px para cima.
  • RN15. A gravação exibida é a que corresponde à largura da tela, pela mesma régua que o app já usa — o useIsMobile, que corta em 600px. Abaixo de 600px toca a gravação de celular; de 600px para cima, a de desktop. Isso responde a faixa de 600 a 1199px, onde o layout é o do mobile (submenu no topo) e a gravação é a de desktop: são duas perguntas diferentes, e a segunda é sobre qual interface a pessoa está usando, não sobre como a página se arruma. Endereço que declara a versão vence a régua (7.3).
  • RN15a. A régua da RN15 vale também na listagem: a duração e a miniatura que o cartão e a linha mostram são as da gravação que aquela pessoa vai assistir. Mostrar a duração de uma gravação e tocar outra faria a lista mentir sobre o que entrega — e as durações diferem de verdade, porque os passos diferem.
  • RN15b. Existindo as duas gravações, a página oferece a outra — uma linha discreta abaixo do vídeo, que troca a gravação no lugar e muda o ?v= (7.1 e 7.2). É regra, e não só prosa da Parte II: sem ela declarada aqui, o único vestígio seria um data-testid e um critério de aceite, e é assim que uma regra some numa reescrita.
  • RN16. A linha de aviso é sobre o descompasso entre a gravação exibida e o aparelho — qualquer que seja a causa. São duas: não existe a gravação daquele viewport (exibe-se a que existe), ou o endereço pediu a outra (?v=, RN17a).

É uma linha só, não duas. Havendo descompasso e existindo a outra gravação, o aviso e a oferta se juntam — "Esta gravação foi feita no computador. Ver a versão para celular." Duas linhas empilhadas dizendo coisas relacionadas fariam ler duas vezes para entender uma. Os dois data-testid continuam existindo, porque o e2e afirma as duas metades separadamente. Correspondendo gravação e aparelho, nada é dito — anunciar o óbvio gasta atenção. - RN17. O assunto tem endereço próprio: /ajuda/<assunto>, e o tutorial, /ajuda/<assunto>/<slug>. Voltar, recarregar e compartilhar preservam os dois (7.4). - RN17a. A versão vai na busca, não no caminho: ?v=desktop ou ?v=mobile. Ausente, vale a régua do aparelho (RN15); presente e correspondendo a uma gravação que existe, vence a régua (RN15 e 7.3). Valor desconhecido (?v=tablet) ou de gravação que aquele tutorial não tem é ignorado em silêncio e vale a régua — um endereço velho não deve virar erro de tela. Busca e não segmento porque a versão é opcional e não faz parte da identidade do tutorial — pôr um quarto segmento obrigaria a inventar um valor para o caso normal, que é justamente o de não escolher. - RN17b. /ajuda/<assunto>/<slug> com assunto que não é o do tutorial redireciona para /ajuda/<assunto-certo>/<slug>, preservando o ?v=. É o que os links compartilhados produzem quando um assunto é renomeado ou fundido, e o tutorial pedido existe — mandar para o lugar certo é melhor que avisar que o caminho mudou, porque a pessoa queria o tutorial, não o caminho. - RN17c. /ajuda sem segmento não redireciona. Ela abre o primeiro assunto e a URL fica como está — redirecionar poria uma entrada a mais no histórico a cada visita, e voltar depois de percorrer três assuntos passaria por ela (CA15). O endereço do assunto aparece quando a pessoa escolhe um. - RN18. /ajuda/<segmento> resolve primeiro como assunto, depois como slug de tutorial (rota da spec 0042, mantida por causa dos links já compartilhados), e só então como inexistente (7.7). A ordem é essa porque assunto é o caminho novo e o normal; a outra é compatibilidade. A RN5a garante que nunca há empate a desfazer.

10. Autorização e isolamento

Não há superfície de autorização nova nesta spec, e isto é afirmação verificada, não omissão. O que se acrescenta é organização de material didático versionado no repositório: nenhum dado de cliente, nenhum endpoint, nenhuma leitura de banco.

10.1 Matriz de papéis

Papel Vê os assuntos e os tutoriais Vê tutorial de assunto que não serve ao seu papel Vê a versão da interface que está usando
ADMIN_TENANT sim sim sim
ANALYST sim sim sim
OPERATOR sim sim sim
VIEWER sim sim sim
AGENCY_USER sim sim sim
Papel customizado sim sim sim
ADMIN_GLOBAL sim sim sim
Não autenticado não — cai no login e volta depois (RN15 da 0042) — —

O "sim" da segunda coluna é a regra, não descuido. A spec 0042 decidiu que a Ajuda não filtra por papel (RN7 de lá): o papel ordena, não recorta. Esta spec mantém a decisão e a estende ao assunto — um assunto cujos tutoriais não servem ao papel de quem olha aparece mais abaixo, e aparece. Aprender o que o outro lado faz é o motivo.

O negativo explícito, e o único que existe: quem não está autenticado não alcança nada da Ajuda — nem lista, nem assunto, nem tutorial —, e é o Authenticated que já guarda a rota hoje. Não há segundo negativo a declarar, porque não há segunda restrição.

10.2 Permissões

Nenhuma criada, nenhuma exigida, nenhuma alterada. Não há resource:action novo, e rbac_seed.py não é tocado — portanto o passo de seed do /ship não tem nada a aplicar por causa desta spec.

Por que não criar uma: permissão existe para recortar acesso a dado. O material aqui é público dentro do produto por decisão da 0042, é igual para todos os tenants e está versionado no bundle que qualquer autenticado já baixa. Uma permissão sobre ele seria um porteiro na frente de uma porta aberta — e daria a impressão falsa de que há algo a proteger.

O que continua condicionado, e não muda: a ação "Ir para a tela" de cada tutorial, que já vem desabilitada com a glosa quando a pessoa não pode fazer o que ele ensina (RN10 e RN10a da 0042). Isso é do tutorial, não do assunto, e esta spec não o toca.

10.3 Isolamento por garantidora (tenant)

Sem impacto, e sem risco. O catálogo não tem tenant_id porque não é dado de tenant: é o mesmo arquivo para todas as garantidoras, compilado no bundle. Não há consulta, não há filtro a esquecer, e não há caminho pelo qual o material de um tenant alcance outro — porque não há material de tenant.

10.4 Isolamento por imobiliária

Sem impacto, pelo mesmo motivo. O partner_agency_id recorta registros de negócio; o material didático não é registro de negócio. Um AGENCY_USER vê os mesmos assuntos que qualquer outro usuário — inclusive os da administração da garantidora, o que é deliberado (10.1).

10.5 Plataforma × cliente (SaaS admin)

O ADMIN_GLOBAL continua sem "Ajuda" no menu do painel do SaaS, e isso não muda: quem passa o item é o layout do app da garantidora, não a casca compartilhada (spec 0042). Se ele alcançar /ajuda pela URL, vê os assuntos e os tutoriais como qualquer autenticado — e a ação "Ir para a tela" permanece sempre desabilitada para ele, antes de qualquer comparação de permissão, com a glosa "estas telas são do app da garantidora" (RN10a da 0042).

O que esta spec acrescenta a esse quadro: nada. Assunto não é dado de negócio, então não há caminho novo pelo qual o admin da plataforma alcance dado de cliente.

11. Impacto por app

apps/api

Sem impacto. Nenhum model, schema, repository, service, router ou migration. O catálogo é arquivo do bundle; criar endpoint para servi-lo seria inventar superfície e trocar um arquivo estático por uma chamada de rede que pode falhar.

apps/web

  • Tipagem: src/tutoriais/tipos.ts — Tutorial ganha assunto com os quatro campos obrigatórios da RN4 (id, titulo, ordem, sintetico) — ordem e sintetico sustentam a RN13, e sair da tipagem é como essa regra se perderia — e troca os campos de gravação por uma lista de Gravacao (viewport, youtubeId, duracaoSegundos, miniatura, roteiro). É mudança incompatível com o formato de hoje, e é a hora de fazê-la: são dois catálogos, ambos regenerados pela skill.
  • Índice: src/tutoriais/index.ts — listarTutoriais passa a devolver assuntos com os tutoriais dentro; ganha acharAssunto e a escolha da gravação por viewport.
  • Páginas: src/pages/ajuda/index.tsx passa a ser o navegador (barra à direita no desktop, submenu no topo no mobile) com a área de conteúdo; TutorialPage.tsx passa a conviver com a barra, a descer o roteiro para baixo do vídeo e a oferecer a outra versão.
  • Componentes: o navegador — a barra e o submenu, que são a mesma lista em duas formas. Ele nasceu em src/pages/ajuda/NavegadorDeAssuntos.tsx, e não em src/components/, por ter um consumidor de cada lado (a listagem e a página do tutorial, ambas em pages/ajuda/): promovê-lo a componente compartilhado é o passo a dar quando houver um terceiro. O TutorialCard muda em três pontos, e a versão anterior desta seção dizia o contrário: ele navega para /ajuda/<slug>, que a RN17 substitui; e lê miniatura e duracaoSegundos do topo do tutorial, que a RN8 move para dentro da gravação e a RN15a manda escolher por viewport. O TutorialRoteiro esse sim continua como está — ele recebe as falas e não sabe de onde vêm.
  • Rotas: /ajuda/:assunto e /ajuda/:assunto/:slug, dentro do porteiro de autenticado, sem exigência de papel; a versão vai na busca (?v=), não no caminho (RN17a). O segmento único resolve primeiro como assunto e depois como slug (RN18): a rota /ajuda/:slug de hoje redireciona para /ajuda/<assunto-dele>/<slug>, preservando o ?v= como a RN17b faz, porque há links compartilhados dela — é o que a 0042 pôs em produção.

As rotas novas ficam sob o mesmo <Route path="/ajuda">, e é isso que preserva a RN13a da 0042 — a Ajuda fora do desvio de onboarding, porque é a única tela que serve a quem ainda não ativou a garantidora. O HomeGuard está só no index; aninhar ali mantém o comportamento sem precisar de regra nova. - data-testid novos: ajuda-barra-assuntos e ajuda-submenu-assuntos (as duas formas do navegador), ajuda-assunto-<id>, ajuda-assunto-item-<slug>, ajuda-linha-<slug> (a linha da lista de títulos abaixo de 600px, RN14a), ajuda-assunto-inexistente (a linha "o assunto que você procurava não existe mais", que a CA13 afirma), tutorial-trocar-versao e tutorial-aviso-versao. Não há ajuda-assunto-vazio — assunto sem tutorial publicado não aparece (7.7), então não há estado a marcar.

Barra e submenu têm marcadores separados — a lista anterior previa um só, ajuda-navegador. Separados, o e2e afirma qual das duas formas está na tela em cada largura, que é o conteúdo da RN11a; com um marcador só, o teste das três larguras passaria sem distinguir uma da outra. - Onboarding: src/pages/onboarding/index.tsx monta /ajuda/<slug> ao concluir a ativação (RN14 da 0042). Passa a montar /ajuda/<assunto>/<slug>. Funcionaria pelo redirecionamento da RN18, mas custaria um salto a cada ativação — e ninguém decidiu pagar isso. - PWA: sem mudança de estratégia de cache. As miniaturas das gravações novas entram como qualquer imagem.

apps/e2e

tests/e2e/ajuda.spec.ts — o spec existente muda, porque a tela mudou: os testes que hoje abrem /ajuda/<slug> passam pela rota nova, e entram casos para o navegador (abrir assunto, escolher tutorial), para a escolha de versão por viewport e para o estado de versão única. Nos dois viewports, sem seed.

scripts/preflight.py

As cinco verificações da spec 0042 mudam de forma, e isso vem antes das novas. A RN8 move youtubeId, duracaoSegundos, miniatura e roteiro do topo do catálogo para dentro da gravação — e é exatamente esses quatro que CHAVES_CATALOGO e CHAVES_ANULAVEIS validam como chaves de topo hoje, e é d.get("miniatura") que a verificação de miniatura lê. Sem ajustar isso primeiro, regenerar os dois catálogos deixa o portão vermelho acusando "falta youtubeId" nos dois, e quem implementa gasta uma rodada entendendo que o erro é do verificador, não do arquivo. Concretamente:

  1. O esquema do catálogo desce um nível: os quatro campos passam a ser validados por gravação, e o topo passa a exigir gravacoes (lista, ao menos um item).
  2. A miniatura é conferida por gravação, não uma por tutorial.

  3. O par roteiro/catálogo passa a ser por gravação. Hoje catalogo_tem_roteiro exige videos/<slug>.json e catalogo_bate_com_roteiro compara contra ele — só o roteiro de desktop. Havendo gravação mobile no catálogo, passa a ser exigido videos/<slug>.mobile.json, e as falas daquela gravação são conferidas contra ele. Sem isso, metade do material novo nasce sem a verificação que a 0042 criou justamente para impedir que os dois arquivos divirjam.

E seis verificações novas, rodando sempre, como as cinco da 0042 e pelo mesmo motivo (são por par de arquivos, e o filtro por linha as deixaria passar):

  1. Todo tutorial declara assunto, e ele existe em assuntos.
  2. Os três campos do assunto batem: o id com o declarado no roteiro, e o titulo e a ordem com o declarado em assuntos. Os dois assuntos sintéticos da RN5b ficam de fora desta verificação — eles não têm entrada em assuntos contra a qual comparar, e a verificação 4 já reprova o mesmo arquivo pelo motivo certo; sem a exceção, o produtor receberia duas mensagens, uma delas sem sentido. A ordem entra pelo mesmo motivo do título — reordenar assuntos sem regerar os catálogos deixaria a barra na ordem antiga, com o portão verde. E dois catálogos do mesmo assunto com ordem diferente também reprovam: é o estado alcançável por regeração parcial, e sem a verificação a ordem passaria a depender de qual arquivo o glob lê primeiro. — três campos copiados a mais, junto dos seis que já se comparam. O título entra porque ele é copiado para o catálogo (RN4) e não está no roteiro: renomear um assunto sem regerar deixaria a tela exibindo o rótulo antigo, sem nada acusar.
  3. Toda gravação declarada tem viewport válido (desktop ou mobile), não há duas com o mesmo viewport no mesmo tutorial, e há pelo menos uma.
  4. Todo identificador de assunto tem forma de slug — minúsculas, dígitos e hífen (RN5c).
  5. Nenhum identificador de assunto coincide com slug de tutorial, e outros não é nem um nem outro (RN5a e RN13).
  6. O roteiro .mobile.json não redeclara nenhuma das cinco chaves que são do tutorial; se tiver publicacao, ela contém apenas descricao; e ele declara papel igual ao do canônico (RN9b).

Depois da entrega são onze verificações de tutorial no preflight: as cinco da spec 0042, três delas reformadas para o formato novo, mais as seis desta spec. Esta enumeração é a que vale. O plano da seção 14 e os critérios de aceite citam este número; onde divergirem, é aqui que se confere.

A skill /tutoriais

  • montar.mjs escreve os quatro campos de assunto no catálogo (RN4), e é o único que os produz. O caminho de assuntos entra no config, como catalogo e miniaturas já entram, e pelo mesmo motivo: videos/publicacoes.json é do projeto, não da skill, e o script recusa por princípio caminho de projeto que o config não declare. Os campos: id vem do roteiro; titulo e ordem, de assuntos — o segundo é a posição na declaração; e sintetico é false, ou true nos dois casos da RN5b. Escrever só os dois primeiros deixaria a RN13 sem o que ordenar e sem como reconhecer o sintético; e escreve a gravação como item da lista, com o viewport do roteiro (ausente = desktop).
  • publicar-youtube.mjs passa a ler assunto onde lia publicacao.playlist, e o comando playlists a ler assuntos onde lia playlists. Passa também a atualizar título e descrição da playlist já criada (RN3b), que hoje ele só cria; a manter duas playlists por assunto, uma por interface, criando a de celular só quando houver gravação dela; e a reconciliar por gravação em vez de por tutorial (RN7b) — hoje ele acha a versão ativa, e com duas a de celular nunca seria conferida. E há três pontos a mais, que falham em silêncio se ficarem para trás: ele lê tut.playlist do registro — o campo que a RN4a apaga — no aposentar e em três lugares do playlists. Sem tratá-los, o aposentar deixa de tirar da playlist o vídeo que tornou privado, e o playlists para de mover tutorial entre listas. Nenhum dos dois dá erro; o efeito é a playlist voltando a acumular versões do mesmo material, que é o defeito que a RN21c da 0042 criou para impedir depois de a lista chegar a quatro cópias.
  • publicar-youtube.mjs, os dois comandos que faltavam. O mapeamento anterior cobria playlists e aposentar e deixava enviar e legenda de fora — os dois publicam em produção, e os dois erram em silêncio com o formato novo:
  • enviar lê docs/videos/<slug>/<slug>.mp4 — caminho que a RN8a muda nas duas dimensões: a pasta ganha o viewport e o arquivo, o sufixo, sobe com o título cru (sem o sufixo da RN7c) e acrescenta o vídeo a uma playlist só (contra a RN7b). Passa a receber --viewport e a usá-lo em quatro pontos: o caminho do arquivo, o sufixo do título, a playlist, e a descrição — que passa a vir do roteiro daquele viewport quando ele a declara (RN9b). Sem o quarto, o vídeo de celular sobe com os capítulos do desktop, apontando para instantes que não existem nele, e sem erro em lugar nenhum.

    Na primeira gravação de celular de um assunto a playlist dela ainda não existe — quem a cria é o playlists (RN7b). O enviar já trata falha de playlist como não fatal, e é o comportamento certo: o vídeo sobe, e a reconciliação seguinte o põe na lista. - legenda lê o .srt com o nome e a pasta novos (RN8a), e resolve o vídeo pela primeira versão ATIVO que encontra; com duas ativas (RN7a), subir a legenda do celular substituiria a do vídeo de desktop que está em produção. Passa a receber --viewport, e lê o .srt com o nome novo. - videos/comum.mjs — arquivo novo, e pequeno de propósito: o sufixo da interface (RN7c), o default de viewport (RN7a) e a leitura do assunto no roteiro (RN4a) governam os dois controles de publicação. Escritos duas vezes, o título que o preparar mostra a quem confere pode divergir do que o enviar manda ao YouTube — a mesma duplicação sem verificação que a RN1 existe para tirar, agora dentro da própria skill. - publicacoes.mjs — além do rename, passa a tratar a versão por (slug, viewport) (RN7a): os comandos publicar e preparar recebem --viewport, e o publicar repassa — ele invoca o montar.mjs com --so-catalogo e --youtube-id, que a RN9a passa a exigir acompanhados do viewport; sem repassar, todo publicar estoura — o segundo porque o título do vídeo de celular leva sufixo (RN7c) —, e o caminho do arquivo que o preparar imprime passa a levar o viewport (RN8a), e a cadeia de versões que ele aposenta é a daquele viewport. É a mudança que impede publicar a gravação de celular e derrubar a de desktop. É impactado e quase passou despercebido. Ele lê d.playlists em três pontos e t.playlist num quarto; com o rename, o comando listar estoura logo na primeira linha. Passa a ler assuntos, e o assunto de cada tutorial vem do roteiro, não mais do próprio registro (RN4a). - narrar.mjs passa a escrever em <saida>/<slug>/<viewport>/ (RN8a) — ele monta a pasta só com o slug hoje, e a narração da versão de celular cairia por cima da de desktop. Estava fora do mapeamento até a última rodada de revisão, e é o quinto script do pipeline. - gravar.mjs passa a ler o viewport do roteiro e a gravar na largura correspondente (RN9c), em vez de usar só a do config.json. - montar.mjs recusa quando a largura carimbada no artefato não corresponde ao viewport declarado, e nomeia miniatura e vídeo com o viewport (RN8a). - SKILL.md ganha o que muda no Passo 3 (o roteiro declara viewport, e o de celular declara só o que difere) e no Passo 6. - Renomeações: publicacao.playlist → publicacao.assunto nos dois roteiros; playlists → assuntos em publicacoes.json. O identificador de cada assunto não muda, então as playlists do YouTube seguem ligadas.

12. Contrato de API

Sem impacto. Nenhum endpoint criado, alterado ou removido. O catálogo é compilado no bundle e lido por import.meta.glob; não há requisição envolvida em nenhum ponto desta feature — inclusive nos estados de erro, que por isso não existem (7.7).

13. Critérios de aceite

  • [ ] CA1. Com dois assuntos publicados, um AGENCY_USER abre /ajuda no desktop e vê a barra de assuntos à direita, com o assunto dos tutoriais dele expandido e os títulos visíveis.
  • [ ] CA2. Clicar num assunto recolhido expande-o e troca a área de conteúdo pelos cartões dele; clicar de novo recolhe e a área não muda.
  • [ ] CA3. Clicar num título abre o tutorial na área de conteúdo, com a barra ainda visível e o roteiro abaixo do vídeo.
  • [ ] CA4. Em 375px, /ajuda mostra o submenu de assuntos no topo e a lista de títulos abaixo — sem cartões —, e não há rolagem horizontal.
  • [ ] CA5. Em 375px a linha de cada tutorial mostra título e duração, sem miniatura; de 600px para cima o cartão mostra miniatura, título, frase e duração (RN14a e RN15a).
  • [ ] CA6. Em 375px, aberto o tutorial, o submenu não aparece, e "Ver o assunto" devolve ao assunto de onde a pessoa saiu.
  • [ ] CA7. Entre 600 e 1199px: sem barra lateral, submenu no topo, e cartões (não a lista enxuta).
  • [ ] CA8. Um tutorial com as duas gravações abre a de desktop no desktop e a de celular em 375px, sem escolha do usuário.
  • [ ] CA9. No desktop, a linha "também tem versão para celular" troca a gravação no lugar, acrescenta ?v=mobile ao endereço e mantém a barra; recarregar mantém a versão trocada.
  • [ ] CA10. Abrir em 375px um endereço com ?v=desktop toca a gravação de desktop, mostra a linha "Esta gravação foi feita no computador" e oferece a de celular — o endereço vence a régua do aparelho (RN17a).
  • [ ] CA11. Um tutorial com só a gravação de desktop, aberto em 375px, toca essa gravação com a linha de aviso; aberto no desktop, toca sem a linha.
  • [ ] CA12. Assunto cujos tutoriais estão todos sem youtubeId não aparece — nem na barra, nem no submenu.
  • [ ] CA13. /ajuda/assunto-que-nao-existe abre o primeiro assunto com a linha "o assunto que você procurava não existe mais", nos dois viewports — e o endereço é reescrito para /ajuda — substituindo a entrada no histórico, não empilhando —, para recarregar não repetir o aviso e para o botão voltar não devolver ao inválido. É /ajuda, e não o endereço do primeiro assunto, porque a RN17c já diz que /ajuda abre o primeiro sem pôr entrada a mais no histórico.
  • [ ] CA14. /ajuda/<slug> (a rota de hoje) redireciona para /ajuda/<assunto>/<slug>.
  • [ ] CA15. O botão voltar do navegador, depois de percorrer três assuntos, devolve ao anterior — não sai da Ajuda.
  • [ ] CA16. Um VIEWER vê todos os assuntos, inclusive os que não servem ao papel dele, e a ação "Ir para a tela" do tutorial de análise continua desabilitada com a glosa.
  • [ ] CA17. Um ADMIN_GLOBAL não vê "Ajuda" no menu do painel do SaaS; alcançando /ajuda pela URL, vê os assuntos e a ação segue desabilitada com "estas telas são do app da garantidora".
  • [ ] CA18. make preflight falha em cada uma das situações novas, uma a uma: tutorial sem assunto; assunto inexistente; gravação sem viewport válido, duplicada ou ausente; gravação mobile no catálogo sem videos/<slug>.mobile.json (esta é a reforma da terceira verificação da 0042, não uma nova); identificador de assunto igual a slug; identificador fora da forma de slug (RN5c); e outros declarado como assunto ou slug.
  • [ ] CA19. make preflight falha quando o assunto do catálogo diverge do declarado no roteiro, e quando o título do assunto no catálogo diverge do declarado em assuntos.
  • [ ] CA20. make preflight falha quando videos/<slug>.mobile.json redeclara qualquer uma das cinco chaves que são do tutorial, ou traz em publicacao outra chave além de descricao (RN9b), e quando a largura carimbada na gravação não corresponde ao viewport declarado — esta última é do montar.mjs, não do preflight: o carimbo vive no artefato de gravação, dentro de docs/videos/, que é ignorado pelo git, e numa árvore limpa o portão não teria o que julgar (RN9c).
  • [ ] CA21. Os dois tutoriais existentes aparecem, cada um no seu assunto, sem terem sido regravados.
  • [ ] CA22. Sem sessão, abrir /ajuda/<assunto> e /ajuda/<assunto>/<slug> leva ao login e, depois de entrar, de volta àquele endereço — as rotas são novas, e o ?to= da RN15 da 0042 nunca foi exercitado sobre elas.
  • [ ] CA23. Um identificador de assunto igual a um slug de tutorial falha no preflight (RN5a e verificação 8). A segunda metade — como /ajuda/<segmento> resolveria o empate — não é critério porque a RN5a torna o empate impossível: um CA precisa ser conferível por quem não escreveu o código.

    Como se conferem os critérios de estado que o portão recusa. Os que exigem tutorial sem assunto ou com assunto inexistente descrevem estados que a RN6 reprova no preflight — então não podem existir numa árvore entregue, e o e2e não tem catálogo de mentira a injetar (o glob leva tudo para o bundle). A prova é manual, no ambiente local: cria-se o estado num catálogo de descarte, abre-se a Ajuda, confere-se, e desfaz-se. Sem isso, os dois são promessa.

    Como os critérios dos scripts da skill são conferidos. CA24, CA25, CA30, CA35, CA37 e CA43 tocam montar.mjs, publicar-youtube.mjs e publicacoes.mjs, que não têm suíte. A prova é a mesma dos verificadores do preflight: executar a situação de propósito num tutorial de descarte no ambiente local, e conferir o arquivo resultante — o registro de publicação, o catálogo e, no caso da playlist, lendo de volta pela API do YouTube. Sem execução, eles são promessa.

  • [ ] CA24. Publicar a gravação de celular de um tutorial que já tem a de desktop publicada não marca a de desktop como A_REMOVER, e o aposentar seguinte não a torna privada nem a tira da playlist (RN7a).

  • [ ] CA25. Montar a gravação de desktop depois de montar a de celular preserva a de celular no catálogo (RN9a).
  • [ ] CA26. /ajuda/<assunto-errado>/<slug> redireciona para o assunto certo, preservando ?v= (RN17b).
  • [ ] CA27. ?v=tablet e ?v=mobile num tutorial só-desktop são ignorados: toca a gravação da régua, sem erro de tela (RN17a).
  • [ ] CA28. Reordenar assuntos sem regerar os catálogos falha no preflight; e dois catálogos do mesmo assunto com ordem diferente também falham (verificação 5).
  • [ ] CA29. Ao fim da entrega, videos/publicacoes.json não tem a entrada tutoriais em assuntos (RN3a) nem a chave tutoriais.<slug>.playlist (RN4a) — que é o que torna a RN1 verdadeira.
  • [ ] CA30. Depois da entrega, node videos/publicar-youtube.mjs playlists renomeia as playlists do canal, e o título de cada uma é o do assunto mais o sufixo da interface — "Análise de crédito pela imobiliária — no computador". O nome exibido na Ajuda é a parte do assunto, sem o sufixo (RN3b e RN7b).
  • [ ] CA31. Com as duas gravações publicadas e durações diferentes, o cartão no desktop e a linha em 375px mostram a duração da gravação que vai tocar ali, e a miniatura correspondente (RN15a e RN8a).
  • [ ] CA32. /ajuda/outros resolve e sobrevive a recarregamento enquanto houver tutorial sem assunto.
  • [ ] CA33. Com um tutorial sem assunto, "Outros tutoriais" aparece ao final; declarado o assunto dele, a gaveta desaparece (RN13).
  • [ ] CA34. Roteiro sem viewport é tratado como desktop (RN10), e item de versoes sem viewport também (RN7a) — o segundo conferido no registro migrado.
  • [ ] CA35. Publicada a gravação de celular, ela entra só na playlist "— no celular" daquele assunto — nunca na de computador —, e o vídeo tem o sufixo no título (RN7b e RN7c).
  • [ ] CA36. Concluir o onboarding com o tutorial de configuração inicial publicado leva a /ajuda/<assunto>/<slug> sem passar pelo redirecionamento de compatibilidade.
  • [ ] CA37. Depois da entrega, cada assunto tem uma playlist no canal — a de desktop, com o título " — no computador"; gestao-imobiliarias tem também a de celular, porque a versão de celular do cadastro-de-imobiliaria é gravada na entrega (seção 5); e imobiliarias, que não recebeu gravação de celular, não tem a segunda lista (RN7b).
  • [ ] CA38. Assunto sem tutorial publicado não aparece na barra nem no submenu, e assunto com pelo menos um publicado aparece (RN12).
  • [ ] CA39. A barra lateral existe a partir de 1200px e some abaixo; a listagem troca cartão por lista de títulos abaixo de 600px (RN11a).
  • [ ] CA40. Um tutorial tem de uma a duas gravações; com zero publicadas ele não aparece na Ajuda (RN11).
  • [ ] CA41. Com as duas gravações publicadas, a página oferece a outra versão numa linha abaixo do vídeo (RN15b); com uma só, a oferta não existe e o aviso aparece quando o aparelho não corresponde (RN16).
  • [ ] CA42. /ajuda sem segmento não redireciona: abre o primeiro assunto e a URL fica como está (RN17c).
  • [ ] CA43. Publicada a primeira gravação de celular de um assunto, o playlists cria a lista "— no celular" daquele assunto e põe só ela lá; a de desktop não é tocada.
  • [ ] CA44. Nada de videos/ no bundle: a verificação de vazamento da 0042 continua passando depois da mudança de formato.

14. Plano de implementação

A ordem não é a canônica api → web → e2e, e o motivo é que não há fase de API. O que desce o contrato aqui é a skill: ela escreve o catálogo, e o catálogo é o que a tela lê.

A ordem entre os passos é de dependência, não de gosto, e dois pontos merecem atenção: o preflight desce o esquema antes de qualquer catálogo ser regenerado, senão a regeneração deixa o portão vermelho acusando "falta youtubeId"; e assuntos migra para o formato com playlists por interface antes do comando que lê esse formato.

Dado e formato

  • [x] Skill — renomear publicacao.playlist → assunto, playlists → assuntos e remover tutoriais.<slug>.playlist (RN4a); atualizar publicacoes.mjs e publicar-youtube.mjs, que leem os nomes antigos em vários pontos entre os dois — a seção 11 enumera quais — o aposentar e o playlists leem registro.playlists e tut.playlist, e o enviar lê pub.playlist
  • [x] Skill — migrar assuntos para o formato com playlists por interface, movendo as duas listas de hoje para a chave desktop (RN3)
  • [x] Skill — migrar videos/publicacoes.json: carimbar viewport: "desktop" nos itens de versoes já registrados (RN7a) — é o que impede a próxima regravação de desktop deixar de aposentar a anterior
  • [x] scripts/preflight.py — o esquema descendo para a gravação, a miniatura conferida por gravação e o par roteiro/catálogo passando a ser por gravação; depois as seis verificações novas, cada uma provada quebrando o arquivo de propósito

Os scripts da skill

  • [x] Skill — narrar.mjs e gravar.mjs escrevendo na pasta por viewport (RN8a); gravar.mjs lendo o viewport do roteiro e gravando na largura certa; montar.mjs recusando descompasso, nomeando os artefatos com o viewport e gravando na pasta por viewport (RN8a e RN9c)
  • [x] Skill — a montagem passando a fundir em vez de sobrescrever, e publicar / aposentar / enviar / legenda operando por (slug, viewport) (RN7a e RN9a) — é o que impede a segunda gravação de derrubar a primeira
  • [x] Skill — o comando playlists mantendo duas playlists por assunto (RN7b), atualizando título e descrição das existentes em vez de só criar as que faltam (RN3b), e reconciliando por gravação
  • [x] Skill — regenerar os catálogos dos dois tutoriais existentes no formato novo, sem regravar, renomeando os .mp4, .png e .srt existentes, e movendo para a pasta por viewport só os que vivem em docs/videos/ — a miniatura fica onde está, em apps/web/public/tutoriais/, que é versionada e é de onde a tela a serve (RN8a); e sincronizar as playlists, o que renomeia as do canal (RN3b)

A tela

  • [x] apps/web — tipagem e índice do catálogo (assuntos, gravações, escolha por viewport)
  • [x] apps/web — o navegador: barra à direita no desktop, submenu no topo no mobile
  • [x] apps/web — a página do tutorial: convivência com a barra, roteiro abaixo do vídeo, troca de versão, linha de aviso
  • [x] apps/web — rotas novas, o redirecionamento da rota antiga, e o destino do onboarding passando a montar /ajuda/<assunto>/<slug>
  • [x] apps/web — desktop, 600–1199px e 375px, com data-testid em todo elemento interativo

O material que torna o eixo verificável

  • [x] Skill — SKILL.md atualizado: o Passo 3 (o roteiro declara viewport, e o de celular declara só o que difere) e o Passo 6 (--viewport nos comandos, duas playlists por assunto)
  • [x] Skill — escrever videos/cadastro-de-imobiliaria.mobile.json, gravar em 375px, montar e publicar. É a gravação de celular que a seção 5 põe em escopo, e sem ela seis critérios de aceite são promessa — inclusive no e2e, que roda contra o bundle do dev-remote e não tem catálogo de mentira a injetar

Fechamento

  • [x] apps/e2e — ajuda.spec.ts reescrito para a tela nova, nos dois viewports
  • [x] make preflight limpo e pytest verde
  • [ ] Validado no browser nas três larguras, e na tela instalada do PWA

15. Decisões em aberto

Nenhuma bloqueia a implementação. As duas abaixo têm comportamento definido no entretempo, e as duas são aditivas — fechá-las depois não desfaz nada do que for construído.

  • [x] A ordem dentro do assunto. Decidida por ora: vale a ordem do catálogo, e não existem "anterior" e "próximo" (RN14 e 7.5). A decisão de declarar ordem por tutorial fica registrada para quando o material crescer; acrescentá-la é um campo no roteiro e dois controles na página.
  • [x] Nome de assunto comprido na barra do desktop. Decidido aqui, para não sobrar para quem implementa: duas linhas e depois reticências, com o nome inteiro no title. Uma linha só, como no mobile, cortaria "Análise de crédito pela imobiliária" em "Análise de crédito pela…", que é onde a informação está. Duas linhas cabem na barra sem empurrar o conteúdo, porque a barra tem largura fixa.

16. Revisão do detalhamento

Rubrica: .claude/rubrics/detalhamento.md — segurança × clareza técnica. Subagente de contexto limpo, novo a cada rodada.

Rodada Veredicto O que travou
1 REPROVADA Cinco Important de contrato: o rename quebrava publicacoes.mjs, ausente do impacto; o assunto seguia declarado duas vezes; as verificações existentes do preflight quebrariam com o formato novo; a URL da versão não existia; e /ajuda/<segmento> era ambíguo.
2 REPROVADA Seis Important, dois com estrago concreto: publicar a segunda gravação aposentaria a primeira, que está em produção; e montar uma apagaria a outra do catálogo, com o portão verde.
3 REPROVADA Sete Important: ordem dos assuntos não derivável do bundle; forma do campo assunto sem esquema; roteiro de celular sem contrato; miniatura colidindo; viewport declarado sem ligação com a largura gravada; três pontos de publicar-youtube.mjs quebrados em silêncio. Um [PERGUNTAR AO USUÁRIO], respondido: título da tela e da playlist são um campo só.
4 REPROVADA Seis Important, um deles afirmação minha factualmente errada sobre o código.
5 REPROVADA Três Important, outra contradição verificada contra o código: a RN3b prometia que o comando renomeia playlist, e ele só cria a que falta.
6 REPROVADA Três Important: reconciliação enxergando uma gravação só; as duas subindo com título idêntico; contagem de verificações divergindo em três lugares. Decisão do usuário: duas playlists por assunto, uma por interface.
7 REPROVADA Três Important: enviar e legenda — os dois últimos scripts do pipeline — estavam fora do mapeamento, e o legenda substituiria a legenda do vídeo em produção.
8 REPROVADA Dois Important: seis critérios exigiam duas gravações publicadas, e gravar a de celular estava fora de escopo — contradição que só se fecha pondo uma gravação em escopo.
9 REPROVADA Um Important, criado pela correção da rodada 8: a gravação entrou em escopo e o plano e um critério continuaram dizendo o contrário.
10 REPROVADA Cinco Important, três deles porque a correção da rodada 9 nunca chegou ao disco — o script de edição abortou no meio e o Python só grava no fim. Relatei como aplicada.
11 REPROVADA Quatro Important: a lista de campos proibidos dizia seis e cobrava sete; o campo papel não estava de lado nenhum, e sem ele o vídeo de celular sairia logado como outro perfil; a CA19 cobrava do preflight uma verificação impossível para ele; e o narrar.mjs — quinto script do pipeline — estava fora do impacto.

| 12 | REPROVADA | Três Important, primeira rodada com o recorte e o spec-lint: a lista de campos proibidos dizia seis num parágrafo e sete no seguinte; o bullet do montar.mjs escrevia dois dos quatro campos do assunto; e a troca de versão mudava a URL sem dizer se empilhava no histórico. | | 13 | REPROVADA | Quatro Important: a descrição do vídeo carrega capítulos com marca de tempo, diferentes entre as gravações, e a regra proibia o roteiro de celular de ter descrição própria; a posição do sufixo no título ficava para quem implementa; dois critérios exigiam um estado que o portão recusa, sem dizer como conferi-los. | | 14 | REPROVADA | Três Important, dois deles criados pela correção da rodada 13: a exceção da descrição contradizia a lista fechada que ela mesma acabara de reescrever, e o comando que publica a descrição não estava na lista de mudanças. | | 15 | APROVADA | Quatro Minor, todos corrigidos: a RN11 sem ancorar a oração, o alvo do endereço reescrito, o caminho de assuntos no config, e a pasta do .srt. |

Resultado: aprovada na rodada 15.

O que as quatro últimas rodadas mediram sobre o próprio portão. A rodada 12 foi a primeira com o spec-lint e com o recorte da parte em revisão. O efeito no custo por rodada foi pequeno — de ~145k para ~139k tokens —, e a razão é medida, não suposta: o revisor gasta a maior parte do orçamento abrindo arquivos do repositório para conferir afirmações, não lendo a spec. Cortar leitura de spec não corta isso.

O que mudou de verdade foi outra coisa. O verificador determinístico tirou da frente a classe mecânica — e pegou, ao vivo, um erro meu na rodada 13: uma regra posta fora de ordem e sem critério, dois minutos depois de eu escrevê-la. E o prompt passou a dizer ao revisor o que não reconferir e a autorizá-lo a aprovar sem perfeição; foi com isso que a rodada 15 fechou, encontrando só quatro achados de redação.

A lição, se houver uma: o gargalo não era o revisor ler demais, era a spec ter defeito demais — e a maior fonte deles fui eu corrigindo um achado e criando outro. Três das quinze rodadas foram isso.

O que estas quinze rodadas dizem, e vale mais que o veredicto. Quase todo achado tem a mesma assinatura: falha silenciosa em pipeline que publica em produção. Aposentar o vídeo errado, apagar a gravação da outra interface, sobrescrever miniatura, legenda e narração, subir dois vídeos com o mesmo título, gravar logado como outro perfil, deixar a playlist sem reconciliar. Nenhuma dá erro; todas põem material errado no ar. A feature mexe em cinco scripts que já publicam num canal público, e o portão levou sete rodadas só para mapear os cinco.

Três erros meus ficam registrados, porque são o tipo que se repete:

  1. Escrever sobre código de memória. Duas vezes o revisor apontou afirmação factualmente errada — "o TutorialCard continua como está" (muda em três pontos) e "o comando renomeia playlist" (ele só cria a que falta). A Parte III é onde se escreve sobre código que já existe, e a memória não serve.
  2. Correção que cria defeito. A rodada 9 reprovou por causa da rodada 8: pôr a gravação em escopo sem ajustar o plano e o critério que diziam o contrário.
  3. Relatar como aplicado o que não foi escrito. Na rodada 10, três achados voltaram porque meu script de edição abortou numa substituição que não casou — e o Python só grava o arquivo no fim, então nada daquele bloco foi salvo. Eu havia relatado sucesso. A correção foi passar a reportar item a item, com "FALHOU" explícito.

17. Revisão da implementação

Preenchido pelo /spec implement, rubrica .claude/rubrics/implementacao.md. As revisões de fase (api, web, e2e) vão nas primeiras linhas; a da branch inteira, na última.

Rodada Escopo Critical Important Minor Veredicto
1 branch 0 4 7 REPROVADA — todos corrigidos

Não houve fase de API (a spec não a tem), e a fase de web foi revisada junto com a branch: o contrato aqui desce da skill, não do servidor.

Resultado: os quatro Important foram corrigidos, e sete dos Minor também.

O que a rodada 1 encontrou, e por que cada um importava:

  • config.example.json sem assuntos. O montar.mjs passou a exigir a chave, que só existia no config local — não versionado. Um checkout novo seguindo o Passo 1 da skill quebrava na primeira montagem. Corrigido no exemplo e na lista do SKILL.md, que também estava desatualizada.
  • O aviso de assunto inexistente não se apagava. A página não desmonta ao trocar de assunto — é disso que o aviso depende para sobreviver à reescrita da URL —, então ele continuava acima dos cartões do assunto seguinte, falando de um endereço que já não era o da tela. Nenhum critério cobria a limpeza.
  • .mobile.json sem viewport sobrescrevia a gravação de computador. O default da RN10 (ausente = desktop) é certo para os roteiros legados e errado para um arquivo cujo nome diz o contrário: os três scripts escreveriam em <slug>/desktop/, a RN9c aprovaria (largura 1280, rótulo desktop, coerentes entre si) e o catálogo perderia a entrada de computador — com o portão verde. É o estrago que a RN8a existe para fechar, entrando pela porta que o default deixou aberta. Agora os três scripts recusam quando o nome e o conteúdo discordam, e o preflight recusa a árvore inteira.
  • CA22 não tinha prova, e ao ganhar uma virou defeito. O critério dizia que o ?to= nunca fora exercitado sobre as rotas novas. Escrito o teste, descobriu-se que ele nunca fora exercitado sobre nenhuma: a RN15 da 0042 supôs que o Refine já honrava o parâmetro, e o authProvider devolvia / em todo login. Quem clicava num link de tutorial compartilhado caía na home. Corrigido lendo o to no login, recusando o que não é caminho interno.

Os Minor corrigidos: a miniatura, que uma correção de paridade havia feito sair na largura cheia em vez da metade; preparar sem validar o --viewport, que imprimia "— undefined" no título da playlist que o operador copia; assuntoDoRoteiro derrubando o laço inteiro num roteiro ausente; o bloco _leia do registro documentando publicar sem o --viewport; a leitura de assuntos engolindo JSON malformado; e a lista de data-testid desta seção, que previa nomes que o código não usou.

Defeitos aceitos: nenhum.

18. Registro

O que a publicação provou, e o que continua por provar

A entrega está completa nos quatro blocos do plano. O que falta é o portão de implementação e a execução do e2e — ele roda contra o dev-remote, e a imagem de lá precisa ser reconstruída antes, senão mede a versão anterior.

Publicar a gravação de celular foi a prova de metade desta spec, e a prova foi executada, não afirmada: registrar a versão de celular não marcou a de computador como substituída (CA24); a sincronização renomeou as duas playlists do canal, criou a de celular do assunto que ganhou gravação, e deixou sem segunda lista o assunto que não ganhou (CA30, CA37, CA43); e a tela passou a escolher a gravação pela largura, com o endereço vencendo a régua e o aviso aparecendo só no descompasso (CA8 a CA11, CA31, CA41).

Dois defeitos só apareceram porque o material foi produzido de verdade. A montagem validava título e frase no roteiro que estava montando — e o de celular não os declara, por regra; e as vinhetas liam o mesmo lugar, então a abertura do primeiro vídeo de celular anunciou "undefined", na tela e na narração, sem que nada acusasse: um mp4 sempre sai. A substituição de vinheta passou a recusar valor vazio. Nenhum dos dois seria encontrado lendo o código, e nenhum dos dois seria encontrado por um catálogo escrito à mão.

O dado e os scripts, e o que a montagem quase apagou sozinha

As duas primeiras fases do plano estão entregues e verificadas: o registro migrou para assuntos com uma playlist por interface, os quatro controles passaram a operar por (slug, viewport), o preflight desceu o esquema para a gravação e ganhou as seis verificações novas — cada uma provada quebrando o arquivo de propósito —, e os dois tutoriais existentes foram remontados pelo caminho de verdade, não editados à mão.

Remontar foi o que expôs o defeito que a leitura não pegaria. A primeira versão da fusão partia do catálogo anterior e escrevia por cima dele; o arquivo resultante ficou com youtubeId, duracaoSegundos, miniatura e roteiro nos dois níveis — os antigos no topo, os novos dentro da gravação. Passava no preflight, que tolera chave a mais, e era exatamente a segunda declaração da mesma coisa que esta spec existe para tirar. A correção foi montar o objeto do zero e preservar do anterior só a gravação da outra interface, que é o que a RN9a pede e nada além.

O número mudou de 0043 para 0045, ao trazer a main

Esta spec foi escrita inteira como 0043. Ao trazer a main antes de implementar, ela veio com 0043-versao-e-novidades e 0044-pagina-de-novidades já mescladas — o contador é sequencial e nunca reutilizado, então quem chega depois renumera, e quem chegou depois foi esta.

Acontece porque ela ficou aberta muitos dias, com quinze rodadas de portão, enquanto outras nasciam e eram mescladas. O número é atribuído na criação, e o mundo continua andando.

Renumerar não é mover a pasta, e é aí que mora a armadilha: as specs se citam dentro dos mesmos arquivos, e uma substituição cega de 0043 reescreveria as referências da outra. O CLAUDE.md, o ship/SKILL.md e o tweak/SKILL.md falam da 0043 de versão e novidades; o spec_lint.py, o recortar.py e o spec/SKILL.md falam desta. A troca foi arquivo a arquivo, separada pelo assunto, e conferida depois nos dois sentidos — que os meus não citassem mais 0043, e que os deles não tivessem ganhado 0045.

A branch continua chamando-se spec/0043-grupos-e-navegacao-na-ajuda, porque renomear branch já publicada quebraria o rastro de quem a tem. O nome é histórico; o número da spec é o do cabeçalho.

O que vem daqui

Anotações durante a implementação: o que mudou em relação ao planejado e por quê. Preencher com /spec implement.