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.
- 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.
- A Parte II diz como se usa o que vai existir. Revisada na seção 8 (jornada × viewports).
- 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.
- 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: emvideos/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:
- A pessoa abre
/ajudapelo item no rodapé do menu. - 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.
- Abre um tutorial. Assiste, com o roteiro em passos ao lado.
- 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.
- Para achar o seguinte, ela repete o passo 2: volta à grade.
- 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:
- Escreve o roteiro em
videos/<slug>.json, decidindo ali a que público serve. - Grava, monta e o
montar.mjsescreve o catálogo emapps/web/src/tutoriais/<slug>.json. - Publica, e aí declara o grupo — em
videos/publicacoes.json, como playlist do YouTube. -
O catálogo que a tela lê não recebe essa informação, e ninguém percebe, porque a tela não a usa.
-
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:
- 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.
- Reconhece o seu sem ler o material dos outros: o nome do assunto basta para descartar o que não interessa agora.
- Abre o assunto que é o dele e vê os tutoriais dele — só os dele.
- 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.
- 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.
7.3 Link e aparelho, quando discordam¶
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
publicacaodo 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.playlistpassa a se chamarassunto, 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 chaveplaylists, que passa a se chamarassuntos. Cada um temtitulo(o que a tela mostra),descricaoevisibilidade. 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:desktopemobile. São gravações do mesmo tutorial — mesmoslug, mesmo título, mesma frase, mesmo assunto, mesmodestino— ensinando o mesmo processo em interfaces diferentes. - RN7a. A identidade de uma versão publicada passa a ser
(slug, viewport), e não mais oslugsozinho. O registro de publicação guarda uma cadeia de versões por gravação: cada item deversoesganhaviewport, 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
viewportdeclarado no roteiro decide a largura da gravação, e não o contrário. Hoje ogravar.mjslê a largura só doconfig.jsone a carimba no artefato; com a chave no roteiro, ele passa a usá-la:desktopgrava na largura do config,mobilegrava em 375px. E a montagem recusa se a largura carimbada no artefato não corresponder aoviewportdeclarado. 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 ogravar.mjsjá 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çãomobiletem de ter sido feita abaixo de 600px, edesktop, de 600px para cima. Exigir largura exata amarraria a spec aoconfig.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
useIsMobileda 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 gavetaoutros, 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 reservadooutrose título "Outros tutoriais". Opreflightrecusaoutroscomo chave deassuntose comoslugde tutorial — sem essa verificação, declararoutrosemassuntospassaria a ser aceito e a gaveta de sobra colidiria com um assunto de verdade./ajuda/outrosresolve 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.globentrega. É arbitrária, e é por causa dela que aordemdo 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 umdata-testide 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—Tutorialganhaassuntocom os quatro campos obrigatórios da RN4 (id,titulo,ordem,sintetico) —ordemesinteticosustentam a RN13, e sair da tipagem é como essa regra se perderia — e troca os campos de gravação por uma lista deGravacao(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—listarTutoriaispassa a devolver assuntos com os tutoriais dentro; ganhaacharAssuntoe a escolha da gravação por viewport. - Páginas:
src/pages/ajuda/index.tsxpassa a ser o navegador (barra à direita no desktop, submenu no topo no mobile) com a área de conteúdo;TutorialPage.tsxpassa 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 emsrc/components/, por ter um consumidor de cada lado (a listagem e a página do tutorial, ambas empages/ajuda/): promovê-lo a componente compartilhado é o passo a dar quando houver um terceiro. OTutorialCardmuda 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êminiaturaeduracaoSegundosdo topo do tutorial, que a RN8 move para dentro da gravação e a RN15a manda escolher por viewport. OTutorialRoteiroesse sim continua como está — ele recebe as falas e não sabe de onde vêm. - Rotas:
/ajuda/:assuntoe/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 comoslug(RN18): a rota/ajuda/:slugde 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:
- 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). -
A miniatura é conferida por gravação, não uma por tutorial.
-
O par roteiro/catálogo passa a ser por gravação. Hoje
catalogo_tem_roteiroexigevideos/<slug>.jsonecatalogo_bate_com_roteirocompara contra ele — só o roteiro de desktop. Havendo gravaçãomobileno catálogo, passa a ser exigidovideos/<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):
- Todo tutorial declara
assunto, e ele existe emassuntos. - Os três campos do assunto batem: o
idcom o declarado no roteiro, e otituloe aordemcom o declarado emassuntos. Os dois assuntos sintéticos da RN5b ficam de fora desta verificação — eles não têm entrada emassuntoscontra 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. Aordementra pelo mesmo motivo do título — reordenarassuntossem regerar os catálogos deixaria a barra na ordem antiga, com o portão verde. E dois catálogos do mesmo assunto comordemdiferente 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. - Toda gravação declarada tem
viewportválido (desktopoumobile), não há duas com o mesmoviewportno mesmo tutorial, e há pelo menos uma. - Todo identificador de assunto tem forma de slug — minúsculas, dígitos e hífen (RN5c).
- Nenhum identificador de assunto coincide com
slugde tutorial, eoutrosnão é nem um nem outro (RN5a e RN13). - O roteiro
.mobile.jsonnão redeclara nenhuma das cinco chaves que são do tutorial; se tiverpublicacao, ela contém apenasdescricao; e ele declarapapeligual 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.mjsescreve os quatro campos deassuntono catálogo (RN4), e é o único que os produz. O caminho deassuntosentra no config, comocatalogoeminiaturasjá 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:idvem do roteiro;tituloeordem, deassuntos— o segundo é a posição na declaração; esinteticoéfalse, outruenos 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 oviewportdo roteiro (ausente =desktop).publicar-youtube.mjspassa a lerassuntoonde liapublicacao.playlist, e o comandoplaylistsa lerassuntosonde liaplaylists. 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.playlistdo registro — o campo que a RN4a apaga — noaposentare em três lugares doplaylists. Sem tratá-los, oaposentardeixa de tirar da playlist o vídeo que tornou privado, e oplaylistspara 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 cobriaplaylistseaposentare deixavaenviarelegendade fora — os dois publicam em produção, e os dois erram em silêncio com o formato novo:-
enviarlê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--viewporte 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). Oenviarjá 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. -legendalê o.srtcom o nome e a pasta novos (RN8a), e resolve o vídeo pela primeira versãoATIVOque 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.srtcom o nome novo. -videos/comum.mjs— arquivo novo, e pequeno de propósito: o sufixo da interface (RN7c), o default deviewport(RN7a) e a leitura do assunto no roteiro (RN4a) governam os dois controles de publicação. Escritos duas vezes, o título que oprepararmostra a quem confere pode divergir do que oenviarmanda 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 comandospublicareprepararrecebem--viewport, e opublicarrepassa — ele invoca omontar.mjscom--so-catalogoe--youtube-id, que a RN9a passa a exigir acompanhados do viewport; sem repassar, todopublicarestoura — o segundo porque o título do vídeo de celular leva sufixo (RN7c) —, e o caminho do arquivo que oprepararimprime 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.playlistsem três pontos et.playlistnum quarto; com o rename, o comandolistarestoura logo na primeira linha. Passa a lerassuntos, e o assunto de cada tutorial vem do roteiro, não mais do próprio registro (RN4a). -narrar.mjspassa 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.mjspassa a ler oviewportdo roteiro e a gravar na largura correspondente (RN9c), em vez de usar só a doconfig.json. -montar.mjsrecusa quando a largura carimbada no artefato não corresponde aoviewportdeclarado, e nomeia miniatura e vídeo com o viewport (RN8a). -SKILL.mdganha o que muda no Passo 3 (o roteiro declaraviewport, e o de celular declara só o que difere) e no Passo 6. - Renomeações:publicacao.playlist→publicacao.assuntonos dois roteiros;playlists→assuntosempublicacoes.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_USERabre/ajudano 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,
/ajudamostra 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=mobileao endereço e mantém a barra; recarregar mantém a versão trocada. - [ ] CA10. Abrir em 375px um endereço com
?v=desktoptoca 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
youtubeIdnão aparece — nem na barra, nem no submenu. - [ ] CA13.
/ajuda/assunto-que-nao-existeabre 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/ajudaabre 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
VIEWERvê 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_GLOBALnão vê "Ajuda" no menu do painel do SaaS; alcançando/ajudapela URL, vê os assuntos e a ação segue desabilitada com "estas telas são do app da garantidora". - [ ] CA18.
make preflightfalha em cada uma das situações novas, uma a uma: tutorial semassunto;assuntoinexistente; gravação semviewportválido, duplicada ou ausente; gravaçãomobileno catálogo semvideos/<slug>.mobile.json(esta é a reforma da terceira verificação da 0042, não uma nova); identificador de assunto igual aslug; identificador fora da forma de slug (RN5c); eoutrosdeclarado como assunto ou slug. - [ ] CA19.
make preflightfalha quando oassuntodo catálogo diverge do declarado no roteiro, e quando o título do assunto no catálogo diverge do declarado emassuntos. - [ ] CA20.
make preflightfalha quandovideos/<slug>.mobile.jsonredeclara qualquer uma das cinco chaves que são do tutorial, ou traz empublicacaooutra chave além dedescricao(RN9b), e quando a largura carimbada na gravação não corresponde aoviewportdeclarado — esta última é domontar.mjs, não dopreflight: o carimbo vive no artefato de gravação, dentro dedocs/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
slugde 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
assuntoou com assunto inexistente descrevem estados que a RN6 reprova nopreflight— 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.mjsepublicacoes.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 oaposentarseguinte 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=tablete?v=mobilenum tutorial só-desktop são ignorados: toca a gravação da régua, sem erro de tela (RN17a). - [ ] CA28. Reordenar
assuntossem regerar os catálogos falha no preflight; e dois catálogos do mesmo assunto comordemdiferente também falham (verificação 5). - [ ] CA29. Ao fim da entrega,
videos/publicacoes.jsonnão tem a entradatutoriaisemassuntos(RN3a) nem a chavetutoriais.<slug>.playlist(RN4a) — que é o que torna a RN1 verdadeira. - [ ] CA30. Depois da entrega,
node videos/publicar-youtube.mjs playlistsrenomeia 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/outrosresolve 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 comodesktop(RN10), e item deversoessemviewporttambé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-imobiliariastem também a de celular, porque a versão de celular docadastro-de-imobiliariaé gravada na entrega (seção 5); eimobiliarias, 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.
/ajudasem 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
playlistscria 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→assuntose removertutoriais.<slug>.playlist(RN4a); atualizarpublicacoes.mjsepublicar-youtube.mjs, que leem os nomes antigos em vários pontos entre os dois — a seção 11 enumera quais — oaposentare oplaylistsleemregistro.playlistsetut.playlist, e oenviarlêpub.playlist - [x] Skill — migrar
assuntospara o formato complaylistspor interface, movendo as duas listas de hoje para a chavedesktop(RN3) - [x] Skill — migrar
videos/publicacoes.json: carimbarviewport: "desktop"nos itens deversoesjá 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.mjsegravar.mjsescrevendo na pasta por viewport (RN8a);gravar.mjslendo oviewportdo roteiro e gravando na largura certa;montar.mjsrecusando 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/legendaoperando por(slug, viewport)(RN7a e RN9a) — é o que impede a segunda gravação de derrubar a primeira - [x] Skill — o comando
playlistsmantendo 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,.pnge.srtexistentes, e movendo para a pasta por viewport só os que vivem emdocs/videos/— a miniatura fica onde está, emapps/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, comdata-testidem todo elemento interativo
O material que torna o eixo verificável
- [x] Skill —
SKILL.mdatualizado: o Passo 3 (o roteiro declaraviewport, e o de celular declara só o que difere) e o Passo 6 (--viewportnos 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.tsreescrito para a tela nova, nos dois viewports - [x]
make preflightlimpo epytestverde - [ ] 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:
- Escrever sobre código de memória. Duas vezes o revisor apontou afirmação factualmente errada — "o
TutorialCardcontinua 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. - 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.
- 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.jsonsemassuntos. Omontar.mjspassou 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 doSKILL.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.jsonsemviewportsobrescrevia 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ótulodesktop, 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 opreflightrecusa 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 oauthProviderdevolvia/em todo login. Quem clicava num link de tutorial compartilhado caía na home. Corrigido lendo otono 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.