ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Transforma texto no formato Frente :: Verso em baralhos prontos para o Anki, em .apkg (dois cliques/um toque e importa sozinho) e .txt. Interface em Português e English. Funciona no navegador, no celular (instalável como app, offline) e no desktop. Código aberto sob licença MIT. EasyAnkiCards turns plain text into ready-to-import Anki decks (.apkg/.txt). PWA — runs in the browser, installable on Android/iPhone, works offline. UI in Portuguese and English. Try it: https://marlitosdev.github.io/easyankicards/ Um cartão por linha; linha em branco separa cartões; # comenta.README 전문 보기 ↗
Use agora, sem instalar nada: https://marlitosdev.github.io/easyankicards/
Transforma texto no formato Frente :: Verso em baralhos prontos para o Anki, em .apkg (dois cliques/um toque e importa sozinho) e .txt. Interface em Português e English. Funciona no navegador, no celular (instalável como app, offline) e no desktop. Código aberto sob licença MIT.
EasyAnkiCards turns plain text into ready-to-import Anki decks (.apkg/.txt). PWA — runs in the browser, installable on Android/iPhone, works offline. UI in Portuguese and English. Try it: https://marlitosdev.github.io/easyankicards/
Um cartão por linha; linha em branco separa cartões; # comenta.
Pergunta :: Resposta :: tags
A capital da França é {{c1::Paris}}. :: Observação :: geografia
[MC] Qual a correta? :: op1 | op2 correta * | op3 :: explicação :: tags
Metadados de cada cartão, em linhas próprias:
@ Título no topo do cartão (linha ACIMA do cartão)
Pergunta :: Resposta :: tags
+ Explicação "Saiba mais" (3 a 15 linhas). O app também aceita "*".
O texto é a única fonte de verdade: o que você edita na tela é reescrito no texto, e vice-versa.
Entrada de texto
{{c1:: e }}) levam fundo, e o texto dentro leva um sublinhado fino — antes a lacuna inteira ficava colorida e metade da tela virava mancha. Grifos em ::, lacunas e [MC] com o texto real editável por cima — seleção do mouse sempre alinhada; interruptor para desligar.Pergunta? / Resposta sem ::, aceita @ e +/* antes ou depois do cartão e separa títulos grudados.**negrito**, a lacuna repetida, o 3º campo que virou frase), botão para editar e aplicar ali mesmo, e setas para percorrer todos os problemas. Frentes repetidas abrem em modo grupo: os cartões iguais lado a lado, com Recortar e Excluir em cada um, para comparar as explicações e escolher qual fica e o botão "Corrigir erros", que só acende quando há algo a corrigir e abre revisão com antes/depois.@@ N e leva junto o cartão dono e o título — a IA devolve os mesmos trechos com as mesmas âncoras e o botão "Colar correção da IA" troca só aqueles blocos, sem encostar no resto do baralho.Tipos de cartão
{{c1::resposta}}, com marcação de lacuna por seleção), múltipla escolha em lista ([MC]) e múltipla escolha na frase ({{c1::correta::opção / opção}}, opções curtas).Conferência e edição
.apkg seguem completos.@ acima e rola/destaca a linha alterada). Selo em cada cartão indicando se usa título próprio, o título geral ou nenhum.@, pergunta, resposta, tags e explicações +) — é o mesmo formato do editor, então o cartão volta completo em qualquer baralho. A bandeja lista um cartão por linha, com seleção, Copiar e Excluir individuais, e permite colar ou descartar só os marcados. Ambos são desfazíveis pelo "Desfazer última colagem".Revisão de conteúdo (botão "Revisar cartões")
Exportação
:: com destino em tempo real..apkg. O bloco "Saiba mais" sai em blocos separados, com o termo em destaque; resposta longa deixa o formato de manchete e vira texto corrido..apkg, com campos "Saiba mais" (link expansível) e Título..apkg gerado no aparelho (SQLite/WebAssembly); no celular abre a folha de compartilhamento → AnkiDroid importa direto. Reexportar o mesmo baralho atualiza, não duplica..apkg só, com subbaralhos por título. Cartões com títulos diferentes (ou de disciplinas/tópicos diferentes) exportam num único arquivo, cada um no seu subbaralho (Baralho::Título, via ::, a mesma convenção que já existia para nomear o baralho) — o Anki importa tudo de uma vez, já organizado. Sem título, continua um baralho só, como sempre foi..txt com coluna de deck (Anki 23.10+): a pasta é criada na importação.Aparência, conforto e confiabilidade
Estudo (modos Edital, Resumos e Questões)
⋮ com os destinos por extenso.easy-anki-cards/
├── docs/ # PWA (web/celular) — servida pelo GitHub Pages
│ ├── index.html · app.js · parser.js · anki.js · i18n.js · modos.js
│ ├── edital.js · editais.js · edital-ui.js · edital-hub.js · pre-edital.js
│ ├── material.js · cartoes-material.js · vinculos.js · vizinhos.js · grifo.js
│ ├── lei-seca.js · lei-ui.js · juris.js · juris-ui.js
│ ├── questoes.js · questoes-ui.js · questoes-hist.js · copiar-questao.js
│ ├── rascunho.js · fora-da-agenda.js · dificuldade.js
│ ├── cartao-melhorar.js · backup.js · backup-ui.js
│ ├── registro-tudo.js · decisoes.js · lixeira.js · plano-log.js · geracao-log.js
│ └── manifest.webmanifest · sw.js · icon-192/512.png · .nojekyll
├── src/easyankicards/ # desktop (janela nativa que carrega docs/) + CLI
├── scripts/build_exe.bat # gera release\EasyAnkiCards.exe
├── tests/ # casos reais + invariantes (node tests/rodar.js)
├── examples/exemplo.txt
├── iniciar_app.bat · requirements.txt · README.md · LICENSE · .gitignore
Cada módulo tem um cabeçalho "MAPA DO ARQUIVO" e "regras de ouro" de manutenção. Regra principal: texto do editor é a fonte única; lógica de negócio em parser.js/anki.js; tela só em app.js; textos visíveis em i18n.js (Português e English, com teste de paridade de chaves).
Desde a v6.4 o aplicativo de desktop é uma janela nativa que carrega os mesmos arquivos de docs/ (via pywebview): mesma interface, mesmos recursos, offline e local. Cada melhoria da versão web chega ao desktop automaticamente. O core.py/cli.py em Python seguem para automação por linha de comando.
Os botões de exportar continuavam aparecendo no modo edital mesmo depois de o código ser corrigido duas vezes. A causa:
<div style="display:flex" id="rodapeExportar"> <!-- hidden não faz nada -->
hidden é aplicado por uma regra da folha de estilo do navegador
([hidden]{display:none}), e um style embutido vence qualquer regra de
folha. O elemento ficava escondido no papel e visível na tela — e como o
código estava certo, cada verificação minha confirmava que estava certo.
O teste S1 agora proíbe display embutido nesse elemento.
Ordenar a semana só por peso agrupa por disciplina: sete horas seguidas de Direito Administrativo, depois oito de Direito Financeiro. Ninguém estuda assim, e quem tenta esquece o primeiro bloco antes de chegar ao fim.
A fila passa a fazer rodízio: a cada rodada entra o tópico mais pesado de cada disciplina que ainda tem fila. A ordem por peso continua valendo dentro de cada disciplina e entre as rodadas; o que muda é que a semana sai misturada. O teste S3 falha se mais de dois tópicos seguidos forem da mesma disciplina.
Duas leituras de peso convivem e é fácil confundi-las:
Σ (peso da disciplina × peso do tópico) de
todos os seus tópicos.Os cartões são ordenados pelo segundo, e cada um mostra a sua fatia
(71% da prova). Direito Constitucional com 26 tópicos e Noções de Direito
Penal com 3 podem ambos estar em "peso 3" e mesmo assim representar fatias
completamente diferentes do que a prova cobra — ordenar pelo 1 a 5 colocaria
os dois lado a lado como se fossem equivalentes.
O empate desempata pelo mais atrasado: entre duas disciplinas de fatia parecida, sobe a menos estudada.
docs/backup.js monta um .json com tudo — cartões, editais, progresso,
diário, material de estudo e preferências. Backup dividido é backup que se
restaura pela metade e deixa o app num estado que ninguém conhecia.
Quatro regras que o arquivo existe para garantir:
backup/1): campo desconhecido é ignorado, campo
ausente ganha padrão — é o que permite restaurar hoje um backup de dezembro;"Salvar na nuvem" não usa credencial nenhuma: no celular abre a folha de compartilhamento do sistema (Drive, OneDrive, Dropbox); no computador deixa escolher uma pasta — se for a do Drive, a cópia sobe sozinha. O selo, no cabeçalho do app, ao lado do número da versão (fase de desenvolvimento — antes ficava dentro de Configurações), mostra de quando é a sua base, verde até 7 dias, vermelho depois de 21.
Cada tópico do edital tem o seu resumo, indexado pela mesma chave do
progresso (disciplina›tópico) — sem tabela de ligação e sem id novo: se a
chave serve para dizer "estudei isto", serve para dizer "e o que eu estudei foi
isto".
Colar de fora preserva a formatação. O texto copiado de uma página
(NotebookLM, Gemini) não traz ** nem ##: readText() devolve o que está
renderizado. A formatação viaja como text/html, e é de lá que ela é lida e
convertida — cabeçalhos, negrito, itálico, listas numeradas e tabelas. As
referências numeradas do NotebookLM ([1], [2, 3]) são removidas, porque só
fazem sentido dentro dele.
Marcar não é editar. No modo leitura, selecionar um trecho e clicar em
destaque, importante ou dúvida grifa sem sair da leitura. A marca é
gravada no texto (==assim==), então sobrevive ao backup, à exportação e a
qualquer versão futura — em vez de virar uma tabela de posições que quebra
assim que alguém mexe numa vírgula. O limite honesto: a marca encontra a
primeira ocorrência do trecho, então frases funcionam melhor que palavras.
A linha da agenda diz, em palavras e sem clique, o que o tópico tem: resumo · 4 cartões · 12 questões · lei seca. Etiqueta só existe quando há material — a
ausência é a informação, e ícone apagado em toda linha era ruído.
A chave tolera a grafia. matChave é exata, e um acento de diferença entre
o nome no edital e o nome com que o resumo foi salvo criava duas gavetas: a
agenda procurava na vazia e apagava os indicadores, com o material inteiro do
lado. A busca da agenda pergunta "existe material deste tópico?", comparando
sem acento — não "existe exatamente nesta grafia?".
As marcas coloridas. Seis cores (==destaque==, ==!importante==,
==?dúvida==, ==§lei==, ==*prova==, ==~pegadinha==), gravadas no texto.
Clicar num trecho pintado abre um menu ali mesmo: trocar a cor, tirar a marca,
ver na lista. Apontar é mais simples que descrever — o botão antigo pedia para
a pessoa selecionar o trecho e então adivinhava qual marca remover, sem mostrar
nada antes de apagar.
Caiu na prova e pegadinha têm painel próprio com contador, e lá dentro cada trecho traz ver-no-texto, editar e tirar a marca.
O * da marca de prova não é negrito. ==* seguido de ** produz ==***,
que o leitor não reconhece como marca nenhuma — a marca não pintava e o
"consertar marcação" acendia no primeiro uso do botão. Um espaço separa os dois.
O ciclo fecha com "Virar cartões", que leva o resumo para a bancada já dentro do prompt de geração: edital decide o assunto → material guarda o conteúdo → cartões fixam a memória.
Cada resumo e cada registro do diário gravam para qual concurso foram feitos. Hoje há um edital só e a marca parece supérflua; ela existe porque informação que não é gravada na hora não se recupera depois — no dia em que houver dois planos, os históricos precisam ser separáveis.
Espaço para o texto. A lista de capítulos, no topo do leitor, competia com
o próprio texto da lei por altura vertical: cada linha reaproveitava o botão
"marcar lido" inteiro (.btn-min, pensado para caber texto por extenso), e a
caixa acumulava preenchimento em cima de preenchimento. Uma classe compacta
só para esse botão e margens mais justas nas fileiras de botões acima da
lista devolveram espaço de leitura sem tirar nenhuma função.
"De onde veio esta lei" mentia duas coisas. O campo de link vinha preenchido com a URL de uma lei específica (a 4.320) como se fosse resposta pronta, mesmo editando outra lei — trocado por um texto de exemplo genérico, sem URL nenhuma. E o campo de data, quando não havia data salva, chutava a data de hoje: abrir a janela só para corrigir o link e clicar "guardar" gravava "consultada hoje" mesmo que ninguém tivesse conferido nada — o campo agora fica vazio até o usuário confirmar de verdade uma data.
O destaque do artigo, ao saltar de uma citação. scrollIntoView já
centralizava o artigo certo, mas o destaque era uma troca estática de cor de
fundo, sem transição — fácil de não notar o instante exato em que a rolagem
parou no lugar certo. Passou a reaproveitar a mesma animação de contorno
pulsante que a busca do material já usa (@keyframes matPisca), com a mesma
guarda de prefers-reduced-motion que o app já respeita em outro lugar.
Nota por artigo. Nenhum campo assim existia. Cada lei ganhou um mapa
opcional, notasArtigos, guardado pelo mesmo mecanismo de merge que já
gravava o resto do registro da lei (leiGuardar) — sem tabela nova. No
leitor, cada artigo ganha um botão de nota (vazio: "+ nota"; preenchido: o
próprio texto, curto); um botão "✨ sugerir com IA" monta um prompt com o
texto do artigo e a resposta cai numa caixa editável, revisada antes de
guardar — a IA sugere, o usuário decide, a mesma regra que vale para
tese e ementa em jurisprudência. A nota aparece em mais dois lugares: no
título do link de uma citação (qs-lei-link), quando resolvido para uma lei
só, e como segunda linha em cada opção do popover de escolher a lei
(qsUiLeiEscolher), quando a citação bate em mais de uma — é o que ajuda a
escolher sem precisar abrir as duas.
"+ guardar mais um julgado" reaparecia com o julgado anterior. jurEditar
preenche os campos do formulário diretamente para editar um julgado
existente; voltar ao modo "incluir" depois disso nunca limpava esses campos,
porque só jurEditar chamava jurLimparForm() — jurTrocarModo("incluir")
não chamava. Um julgado novo nascia com o texto do anterior ainda nos campos.
A correção foi uma linha (if (m === "incluir") jurLimparForm();), validada
revertendo-a de propósito e confirmando que o teste novo falha sem ela.
O prompt do 🩹 "completar e conferir" tinha erro de concordância. "Faltam
1 campo(s)" para um campo só lia mal; a mensagem agora tem duas formas —
singular e plural — escolhidas por uma condição simples, o mesmo padrão de
chave dupla (_um) já usado em outros textos do app em vez de um "(s)"
colado.
Melhorar o resumo, separado de preencher e de corrigir. O botão "✨ perguntar à IA" já existia, mas serve para extrair metadados de uma ementa colada (tribunal, classe, data) — nunca reescreve nada. "Corrigir lacunas" (🩹) já aponta campos vazios, também sem reescrever. Faltava mesmo melhorar a redação do resumo, e só do resumo: a tese continua proibida de ser reescrita, por ser transcrição do tribunal. O botão novo (💡, ao lado da caixa de resumo) abre uma janela no mesmo formato do "cartão-melhorar" já existente no app: gera um prompt → copiar → colar a resposta da IA → conferir antes/depois lado a lado → só então aplicar. Tese e ementa entram no prompt como contexto, nunca para a IA devolver; "aplicar" escreve o texto novo só na caixa do formulário — salvar continua exigindo o "Guardar" de sempre, a mesma regra de "marcar é experimentar" que já vale para os grifos da lei seca.
Banco próprio (eac_questoes), separado dos cartões: cartão fixa memória,
questão treina julgamento, e misturar os dois faria um contador mentir sobre o
outro.
Criar. Duas portas na barra do resumo, cada uma dizendo o que vai levar:
questões do trecho selecionado (1.331 caracteres) e questões do resumo inteiro (12.548 caracteres). Os dois números lado a lado deixam conferir, sem
abrir nada, o que cada caminho manda para a IA. O primeiro fica apagado
enquanto não houver seleção — assim se vê, sem ler, que falta selecionar.
Questões já escritas dentro do resumo (?> enunciado / >> gabarito) são
detectadas e viram respondíveis com o gabarito oculto.
Responder. A rodada sobrevive a fechar a janela. Dá para pular, embaralhar o que falta, e ligar "só as que errei" no meio da resolução — que não recorta a fila, só muda o que o "próxima" pula, então desligar devolve a rodada inteira com o placar intacto. Antes de responder, o histórico daquela questão aparece como placar, nunca como letra: dizer qual alternativa foi marcada da última vez, numa questão de certo/errado, é contar a resposta.
Rascunho. Um papel de lado, recolhido por padrão, com quatro canetas (preta, azul, vermelha, verde), borracha e desfazer. Funciona a dedo, mouse ou caneta pelo mesmo caminho de código. O desenho é guardado como traços, não como imagem: um PNG por questão estoura o armazenamento do navegador em poucas dezenas de rascunhos — sem aviso, derrubando outros dados junto. Cada questão tem o seu papel, e nada é salvo sem você mandar (mas sair com rabisco na tela pergunta antes).
Encerrar agora (2/5 · 40%). Fecha a rodada com o placar que estiver e leva ao registro de estudo. Duas confirmações, porque o gesto é irreversível para a rodada. Antes só havia responder tudo ou fechar a janela — e fechar não registrava nada, então a hora de estudo evaporava.
Melhorar esta questão. Questão gerada por IA sai torta com frequência, e a única saída era apagá-la, perdendo junto o histórico e a dica escrita à mão. O detector nomeia o defeito e o botão se anuncia: ✏️ melhorar esta questão (5 problemas). O caso que originou isto tinha cinco de uma vez — a resolução dentro do enunciado (a questão se responde sozinha), conversa da IA copiada junto ("Que tal criarmos um simulado?"), cabeçalho de simulado, markdown cru e emoji.
O prompt não pede "melhore": lista os defeitos encontrados, porque "melhore esta questão" devolve outra versão do mesmo problema — a IA não sabe o que incomodou. A correção mostra antes e depois lado a lado, e aplicar mantém o id, as tentativas e a dica: é a mesma questão com texto novo.
Há duas situações em que o item certo está na frente e mesmo assim não é o que fazer agora — e elas parecem a mesma coisa, porque as duas tiram o item da tela. Não são, e a diferença aparece no número que importa: quanto falta estudar.
| o que é | o que acontece | |
|---|---|---|
| adiar | não é a hora (semana cheia, foco em outro concurso) | volta sozinho no prazo e continua na conta do que falta |
| dispensar | já está coberto (estudei para outro edital, já domino) | sai da conta, e as horas aparecem em cor própria |
Tratar as duas como uma só produziria um de dois erros: ou a agenda cobra eternamente o que já foi visto, ou o plano encolhe cada vez que a semana está cheia — e a pessoa chega na prova achando que estudou.
Por isso o motivo decide o tipo. Ninguém escolhe entre dois botões parecidos: escolhe-se o porquê, que é o que a pessoa sabe, e a consequência vem junto. No medidor da semana as horas dispensadas ganham faixa hachurada própria: não são horas estudadas nem horas perdidas.
A crítica que originou a v10 estava certa: roxo neon, verde esmeralda, vermelho, laranja e cinza ao mesmo tempo, e subtextos ilegíveis.
Contraste medido, não opinado. No tema preto, o roxo dava 3,33:1 e o cinza 3,93:1 — os "textos apagados". A causa: as cores nasceram como fundo de botão (escuras, com letra branca por cima) e foram reaproveitadas como cor de texto. Cada cor passou a ter um par — a de encher e a de escrever — e um teste calcula o contraste de todos os pares nos três temas, falhando abaixo de 4,5:1. Nos dois sentidos: o rótulo branco sobre o fundo colorido também é medido.
Uma cor de ação. Verde, azul, roxo e ciano viraram azul. O verde saiu dos botões e passou a significar exclusivamente resultado — gabarito certo, progresso, acerto no placar. Botão verde ao lado de gabarito verde era a mesma cor dizendo duas coisas. Três papéis e só: primário, secundário e destrutivo.
Ação destrutiva não fica na linha de frente. "Apagar" em vermelho ao lado de
"responder" atraía mais o olho que a ação principal — e o alvo errado num toque
de celular leva junto o histórico da questão. Foi para um ⋮, a um toque de
quem o procura e a nenhum de quem não procura.
A linha da agenda tem três alvos, não seis. "Estudar" leva ao que o tópico
tem (resumo, ou cartões se não houver resumo, ou a criação se não houver nada);
o ⋮ guarda o resto, e é lá dentro que cabe a palavra escrita por extenso —
"ver os 4 cartões", "responder as 12 questões". Trocar os ícones por rótulos na
própria linha teria piorado: cada linha ficaria três vezes mais alta.
O card da disciplina responde uma pergunta só: e agora? Fechado, tem nome, uma barra e o próximo tópico — o de maior peso ainda não estudado, ignorando o que foi adiado. Os seis números de antes (estudados, revisados, porcentagem, fatia da prova, bolinhas das faixas) não sumiram: foram para "números e tópicos", que é onde se vai quando a pergunta deixa de ser "e agora?" e passa a ser "como estou nesta matéria?".
O filtro de disciplinas é uma linha fechada. Dezessete disciplinas viravam
dezessete pílulas empilhadas, mais altas que a própria agenda. Agora:
disciplinas (3 de 17) ▾, com busca sem acento dentro e um "desmarcar todas"
— porque quem quer ver uma de dezessete não vai clicar em dezesseis.
Registrar pergunta quanto e como, porque é isso que permite, meses depois, dizer "você lê muito e resolve pouca questão".
Quem resolveu no papel raramente lembra "34 de 40"; lembra "uns 85%". Um marcador troca as duas contagens por um campo único de percentual, e o diário grava "87% de acerto (sem contagem)" em vez de fingir uma contagem que não houve. Campo em branco não vira 0% — "não informei" e "0%" são coisas diferentes.
O ponto colorido da linha explica a própria cor ao passar o mouse: "Prioridade: estudar primeiro. Vem de peso 5 da disciplina × peso 5 do tópico = 25." Cor sem legenda é enfeite; com legenda, é o argumento que faz aceitar (ou contestar) a ordem da agenda.
Contar tópicos trata Noções de Direito Penal (3 tópicos) e Direito Constitucional (26) como iguais. O painel mede as duas coisas, e é a diferença entre elas que informa:
| quem estudou | tópicos | peso da prova |
|---|---|---|
| os 4 tópicos leves | 67% | 7% |
| os 2 tópicos pesados | 33% | 93% |
Mesma pessoa, mesmo esforço aparente, resultados opostos. Quando a contagem de tópicos passa 10 pontos à frente do peso, o painel avisa: "o esforço está indo para os assuntos mais leves".
Cada tópico tem dois estados, marcados pelo usuário: estudado (a caixa) e
revisado (o botão R, que só aparece depois de estudado — não se revisa o
que não se viu). Revisado é um subconjunto de estudado, nunca uma soma à
parte, e por isso a barra tem duas camadas em vez de duas barras: verde claro
o estudado, verde escuro o revisado dentro dele.
O progresso salvo no formato antigo (true) continua valendo — a migração é
silenciosa, feita na leitura, para ninguém perder o que já marcou.
Um cartão por disciplina, sempre abertos, competia por altura com a agenda da
semana logo acima — em dezessete disciplinas, a rolagem até "e agora?" ficava
longa antes mesmo de chegar lá. O painel virou um mapa recolhível: fechado
por padrão, mostra só um resumo por extenso (8 disciplinas · 231 tópicos · 64% feito) e uma seta; abrir revela os cartões de cada disciplina, com um
seletor de ordem — por lacuna (o que falta, padrão do panorama já
existente em edital.js), por fatia da prova, ou pela ordem do próprio
edital.
Nem o estado aberto/fechado nem a ordem escolhida são lembrados entre sessões
(ao contrário de outras gavetas do app, como edAbertas): é estado de leitura
da tela agora, não preferência — abrir o mapa toda vez que a página carrega
seria pior do que fechado por padrão, mas lembrar uma ordem antiga na sessão
seguinte também confundiria mais do que ajudaria.
Duas vistas, e a que abre por padrão é a do dia a dia. A tabela de 231 linhas responde "qual é a ordem?", pergunta que se faz uma vez. O painel responde "e agora?", que se faz toda manhã:
● 12 alta · ● 9 média · ● 5 baixa) e o peso editável ali mesmo.Mudar o peso no cartão reescreve o texto, nunca um estado paralelo: enquanto tela e texto puderem divergir, uma das duas está mentindo e o usuário não tem como saber qual. O teste O6 existe só para isso.
Sobre o registro: não há um log separado do edital, e não deve haver. É um
registro só, com os eventos marcados por prefixo (EDITAL-TEXTO,
EDITAL-PESO, EDITAL-PROGRESSO) — dois registros criariam a pergunta "em
qual deles está?", cuja resposta certa costuma ser "nos dois, e a ordem entre
eles importa". O que faltava era conseguir filtrar, e agora o painel de
diagnóstico tem a caixa "mostrar só os eventos do modo em que estou".
O primeiro modelo dividia o orçamento semanal entre todos os tópicos. Com o edital real de um usuário — TCE-PE, 231 tópicos, 12h por semana — o resultado foi 20 minutos para cada um, ou seja 77 horas por semana: seis vezes o tempo que existia. Aritmeticamente correto, completamente inútil.
Pior: um teste (I-E5b) chegou a fixar esse comportamento como certo, com o
comentário de que "o piso estoura o orçamento de propósito, e isso é honesto".
Era honesto e inútil — um plano que ninguém pode cumprir não informa nada.
O modelo agora é uma fila ao longo das semanas até a prova:
orçamento = semanas × horas/semana, preenchido em ordem de prioridade. O que
não cabe é contado, nomeado e mostrado — com quantas horas por semana cobririam
tudo. Para o mesmo edital: 108h de orçamento, 108 tópicos cabem, 123 ficam de
fora, seriam necessárias 24h por semana.
Tempo por faixa, não proporcional: alta 60min, média 45, baixa 30. Proporção entre 231 itens produz "8 minutos de Direito Civil", que não é uma sessão de estudo.
E um detector novo, do mesmo tipo do "cartão preso à prova de origem": todas as disciplinas com o mesmo peso. Foi o que a IA devolveu no caso real — 17 disciplinas, todas com 3 — e como a prioridade é peso da disciplina × peso do tópico, a disciplina sai da conta e a ordenação vira quase um empate. Formato válido, priorização inexistente.
A regra que resolveu a confusão da barra da bancada: o que é do dia a dia fica junto do trabalho; o que é rede de segurança fica em Ferramentas.
| botão | onde | por quê |
|---|---|---|
| ⤢ Ampliar | ao lado do editor | usado o tempo todo enquanto se cola texto grande |
| ⟲ Versões do texto | Ferramentas | rede de segurança, usada em acidente — não é tarefa diária |
| Importar PDF/Word/Excel | Ferramentas | uso ocasional que ocupava o topo da bancada todo dia |
| Aviso de encolhimento | na bancada, quando acontece | é contextual: aparece no instante do acidente e some depois |
E duas regras de linguagem que valem para os três:
Gerar cartões a partir de um tópico do material abria uma segunda tela,
separada da Bancada de cartões de verdade: mesmo prompt, mas a resposta da IA
caía numa caixa de texto solta, sem prévia, sem legenda de sintaxe, sem os
avisos de cartão longo ou repetido. Os cartões que saíam de lá eram mais
fracos não porque o prompt fosse pior — é o mesmo texto, com o mesmo
montarGen() — e sim porque nunca passavam pelo filtro que a Bancada real já
aplica a tudo.
A correção não duplicou nada: fechar aquela tela leva para a Bancada, com um
aviso de que uma resposta está pendente, e "Colar mais texto" — o mesmo
botão de sempre — recebe o que a IA devolveu. Como esse botão só anexa
(com uma linha em branco separando o que já havia do que chegou agora), o que
você já estivesse escrevendo na Bancada continua lá; era esse o cuidado que
não podia falhar. Dali em diante os cartões gerados por tópico passam pela
mesma pré-visualização, legenda e avisos de qualquer outro cartão — e ganham
de graça a exportação em .apkg/.txt, que a tela antiga nunca ofereceu.
Guardar no material só aceita o que já passou por essa verificação; guardando
com sucesso, um botão "estudar agora" leva direto para a revisão daquele
tópico.
O relatório segue o modo. Ele olhava sempre a bancada de cartões, e com o edital no ar isso virou mentira: quem relatasse um problema do edital recebia de volta o texto dos cartões, sem nenhum sinal da troca. Foi assim que a pergunta "o edital veio completo?" ficou sem resposta — o log tinha o prompt sendo aberto e mais nada, e o relatório mostrava outra bancada.
Agora o painel declara em cima, por extenso, de qual bancada é o relatório, com linhas, caracteres e eventos; copiar e baixar confirmam no próprio botão ("✓ Copiado") além do aviso passageiro. E o bloco do edital traz as contagens que respondem à pergunta: disciplinas, tópicos, pesos chutados, linhas não entendidas, e quantos tópicos cada disciplina recebeu — que é onde se vê um edital cortado no meio.
[EDITAL-TEXTO] registra as contagens a cada mudança real (com pausa, para
digitar não gerar duzentas linhas). O histórico delas é o que mostra o edital
chegando pela metade.
Eram dois links no rodapé que copiavam cegamente para a área de transferência.
Quem clicava não via o que estava levando, e a diferença entre "Diagnóstico" e
"Registro" não estava escrita em lugar nenhum. Agora é um botão que abre um
painel: explica cada bloco, mostra o relatório com as divisórias e os erros
destacados, e daí você copia ou baixa .txt. Uma caixa deixa tirar o texto dos
cartões do relatório, para quem não quer enviar o próprio material.
O botão saiu do menu de Configurações e foi para o cabeçalho, ao lado do selo da base e do número da versão — junto do resto do que só interessa durante o desenvolvimento do app, e fora do caminho de quem só quer estudar. "Leitura automática: N cartões", que reescrevia a mesma área de status a cada tecla digitada e acabava apagando avisos de outras ações, foi desligada.
Quatro correções de fundo, cada uma nascida de uma falha real:
| o que mudou | veio de | |
|---|---|---|
| Blocos protegidos | cada seção do relatório entra num try; se uma falhar, o buraco fica anotado e o resto sai | o diagnóstico rodava sem rede — e ele roda exatamente quando as coisas estão quebradas |
| Eventos que não se descartam | ERRO, BLOQUEIO, APAGAR, RESTAURAR e TEXTO ficam no anel; a rotina é que sai | seis [INICIO] seguidos empurravam para fora um [ERRO] de dias atrás |
| Código de sessão | quatro caracteres por abertura, em cada linha do registro | com PWA e navegador abertos juntos, os eventos se intercalavam sem ordem legível |
| Armazenamento no relatório | persist() concedido?, espaço em uso, versões no histórico | era o dado mais relevante para a perda de 137 cartões, e o único ausente |
E três eventos que faltavam: EXPORTAR, COLAR-EDITOR e DIAGNOSTICO. O
primeiro é o que responde à pergunta que decidiu tudo naquele incidente —
existia uma cópia fora do app? — e que o registro, com 134 eventos, não sabia
responder.
tests/tela.js cobre o diagnóstico com seis entradas degeneradas (vazio, cloze
sem fechar, só separador, 5.000 caracteres): um relatório de defeito que falha
junto com o defeito é pior do que nenhum.
docs/modos.js é um registro, não um menu com um if por botão:
const MODOS = [
{ id: "cartoes", secao: "secCartoes", icone: "🗂", rotulo: "modo_cartoes", pronto: true },
{ id: "edital", secao: "secEdital", icone: "📋", rotulo: "modo_edital", pronto: true },
{ id: "material", secao: "secResumos", icone: "📚", rotulo: "modo_material",
rotuloCurto: "modo_material_curto", pronto: true },
{ id: "questoes", secao: "secQuestoes", icone: "❓", rotulo: "modo_questoes", pronto: true },
{ id: "ferramentas", secao: "secFerramentas", icone: "🧰", rotulo: "modo_ferramentas", pronto: true },
];
Um modo novo é uma entrada aqui mais uma <section> no HTML — app.js,
parser.js e anki.js não mudam. Cada entrada aponta a própria seção em vez
de o código procurar por [data-modo]: assim o registro é a única fonte da
verdade e dá para saber tudo sobre os modos sem abrir o HTML. rotuloCurto é
opcional — só quem precisa de um rótulo mais estreito para caber na fileira
única do celular o declara (hoje só "material"); os outros repetem o rótulo
cheio, e é o CSS, não o JS, que decide qual dos dois mostrar.
Todos os cinco modos deixaram de ser esqueleto: o edital tem plano, agenda, diário e acompanhamento; o material tem marcas, dicas, lei seca, jurisprudência e cartões; as questões têm banco, sessão que sobrevive a fechar a janela, rascunho e correção por prompt. A regra que o teste K5 protege desde a época do esqueleto continua valendo: trocar de modo não encosta no texto do editor. Um modo que apagasse o trabalho do outro repetiria o acidente que custou 137 cartões.
Trilho vertical acima de 760px, fileira única abaixo disso — mesma
marcação, montada pelo mesmo registro; o breakpoint é 760px porque é o que o
resto do app já usa (lei seca, agenda, diálogos), não mais 900px, que era a
exceção e derrubava notebooks um pouco mais estreitos no modo celular sem
avisar. Um bug de layout apareceu junto com essa correção: a barra usava
float, e position:sticky só funciona quando o próprio contentor do
elemento tem altura no fluxo normal — um pai flutuante colapsa sem clearfix, e
a barra sumia ao rolar a tela. A troca de float por display:flex resolveu
as duas coisas de uma vez.
A faixa de modos (Cartões/Edital/Material/Questões/Ferramentas) tinha dois
problemas juntos: no computador ela usava float, e ao rolar a tela ela
desaparecia — position:sticky só funciona quando o próprio contentor do
elemento tem altura no fluxo normal, e um pai flutuante colapsa sem
clearfix. A inspeção ao vivo (getBoundingClientRect() no console) mostrou
a barra com top:-700 e o contentor com height:22 — o pai tinha
encolhido para quase nada, e a barra "sticky" flutuava para fora da tela
junto com ele. A troca de float por display:flex resolveu isso e, de
quebra, simplificou o CSS: um layout flex não precisa da mesma cerimônia de
clearfix que o float exige.
No celular, cinco ícones divididos entre trilho vertical e fileira de abas
conforme a largura já existiam (ver Modos); o ajuste
aqui foi de acabamento — cores por modo aplicadas de fato (a regra de
"material" e "questões" nunca tinha pegado, por causa de um id antigo
resumos que não existe mais no registro) e o breakpoint alinhado ao resto
do app, 760px em vez de 900px.
O defeito mais caro que os prompts produziam não era de formato — era de conteúdo, e passava em toda verificação:
Qual a ordem correta das definições da Questão 17?
:: A ordem correta é a Letra A: (1) Avaliação, (2) Formulação de Agendas
Formato impecável. Daqui a dois meses, quando ninguém tem a prova na frente, não ensina nada: pergunta onde estava a resposta, não o que a coisa é. A IA produz isso quando recebe uma lista de questões — preserva com fidelidade o andaime (número, letra, gabarito) e joga fora o conceito.
Todos os seis prompts passaram a carregar a mesma regra de ouro, colocada ANTES do material (depois dele, perde força): o cartão se explica sozinho, é proibido citar questão/letra/gabarito, a resposta é o conceito e não a sua localização, e dividir é redistribuir — nunca resumir.
Mas prompt é pedido, não garantia. Por isso existe também um detector, e ele só aponta: reescrever exige entender o assunto, então o aviso vai marcado como "precisa da IA", com "Ver no texto" e a crítica por linha dentro do prompt de correção.
Até a v8.44 o texto do editor morava num único slot de localStorage,
sobrescrito 400ms depois de qualquer digitação. Selecionar tudo e colar outra
coisa apagava horas de trabalho de forma definitiva — e silenciosa, porque as
duas ações mais destrutivas do app (substituir o texto e "Apagar tudo") eram
justamente as únicas que não geravam evento no registro. Um usuário perdeu 137
cartões assim, e o registro não sabia dizer o que tinha acontecido.
Agora o app guarda uma cópia antes de cada mudança grande (até 12 versões,
teto de ~1,2 MB), o botão ⟲ Histórico lista e restaura, e um encolhimento
brusco do texto — metade ou menos, em texto de trabalho — levanta uma barra
vermelha oferecendo a versão anterior de volta. [TEXTO], [APAGAR] e
[RESTAURAR] passaram a aparecer no registro.
Isso é uma rede para acidentes das últimas horas, não um sistema de versões.
O artefato durável continua sendo o .apkg exportado e importado no Anki.
node tests/rodar.js roda tudo
node tests/rodar.js --gravar congela os números atuais em esperado.json
(no Windows, dois cliques em tests\rodar.bat)
Para adicionar um caso: salve o texto problemático como tests/casos/NN-nome.txt. Só isso — as invariantes já passam a valer para ele; rode --gravar para congelar os números.
São dois tipos de teste. As invariantes valem para qualquer texto e pegam o bug que ninguém imaginou:
| regra | veio do bug | |
|---|---|---|
| I1 | ler um texto nunca quebra o app | — |
| I2 | nenhuma correção apaga cartões | v8.19 |
| I3 | nenhuma correção apaga o "Saiba mais" | v8.22 |
| I6 | nenhuma correção apaga etiquetas | v8.23 |
| I4 | corrigir 2x = corrigir 1x (idempotência) | v8.19 |
| I5 | ida e volta (texto → cartões → texto) não muda nada | — |
Duas invariantes de dado que nasceram do mesmo susto — perda silenciosa: texto vivo (o que está na caixa) e texto gravado (o que está no registro) não podem divergir, senão a próxima gravação apaga o que a tela mostrava; e nada sai da tela sem que a pessoa veja o que vai sair, seja marca, questão recusada por semelhança, ou rascunho ainda não guardado.
A lista de sugestões marca quem resolve cada problema, e a marca vem com consequência: todo item de IA sabe dizer em que linha ele acontece ("Ver no texto"), o crachá traz um ícone que explica o caminho, e o botão "Criar prompt de correção" pulsa enquanto houver trabalho que só a IA faz. Detalhe: o app arruma o que é mecânico (formato, espaço, markdown); dividir um cartão longo ou encurtar uma alternativa é editorial e vai para a IA pelo prompt de correção. Sem essa marca o usuário clica em "Corrigir" esperando que resolva tudo e conclui que o app está quebrado.
Há também um detector para o pior acidente do fluxo: o prompt colado de volta no lugar do material. Ele não olha sintaxe — os cartões que a IA gera a partir das próprias regras são formalmente perfeitos — e sim vocabulário. Por isso só avisa e oferece limpar, nunca apaga sozinho: um baralho sobre método de estudo falaria dessas mesmas palavras por direito.
apkg.js olha o arquivo que sai, não o app: confere que o alinhamento vai declarado no CSS exportado (e não herdado do modelo padrão do Anki, que centraliza tudo), que os conceitos do "Saiba mais" saem em blocos separados, e que o guid de cada nota vem do texto que você escreveu — não do HTML gerado. Essa última é a que evita a pior surpresa: enquanto o guid saía dos campos já formatados, qualquer melhoria de apresentação mudava a identidade de todos os cartões e reimportar o baralho duplicava tudo em vez de atualizar.
estrutura.js confere que as tags do HTML fecham na ordem certa e que cada elemento continua dentro do painel a que pertence — uma tag sobrando não quebra nada visivelmente, o navegador "conserta" e o resultado é um pedaço da tela na coluna errada. Com o tempo ele virou o lugar das regras que valem para a tela inteira:
| regra | veio do bug | |
|---|---|---|
| E6 | todo script do index.html está no SHELL do sw.js | app quebrado offline para quem já tinha instalado |
| E7 | nenhum <dialog> ganha display sem exigir [open] | janelas que não fechavam e se acumulavam embaixo da tela |
| E8 | [hidden] vence qualquer display de classe | painel que ficava visível apesar de escondido |
| E9 | todo clique ligado no JS aponta para um id que existe no HTML | "virar o trecho em questão" morreu calado: o código do clique continuou inteiro, o botão sumiu do HTML, e o if ($(id)) engoliu a falta |
| E10 | contraste de texto ≥ 4,5:1 nos três temas, nos dois sentidos | campo de acertos branco no branco; roxo a 3,33:1 no tema preto |
| E11 | fundo de botão sai de um token de papel, nunca de cor fixa | cinco cores de ação escolhidas por gosto |
E9 e E10 pegam a mesma classe de defeito: o que falha sem dar sintoma. Um botão que não existe não dá erro no console; uma cor ilegível não quebra nada. Os dois só aparecem quando alguém tenta usar — e reclama.
Rodam junto os testes de interface, num DOM mínimo escrito à mão (sem dependência): fumaca.js verifica que o app carrega sem erro, tela.js exercita revisão, prompt de correção, bandeja e foco, e mais material-marcas.js, material-dicas-questoes.js, questoes.js, questoes-ui.js, rascunho.js, fora-da-agenda.js, registro.js, edital.js, editais.js, vinculos.js, pre-edital.js, cartoes-material.js, apkg.js e colagem.js.
Cada melhoria do DOM simulado revelou um defeito real. Enquanto textContent devolvia só o texto do próprio nó, qualquer painel montado por partes era medido como string vazia. Enquanto classList era um no-op, todo comportamento baseado em classe passava sem ser exercido. Enquanto os atributos do HTML eram ignorados, "nasce escondido" era indemonstrável. E enquanto getContext("2d") não existia, "desenhei e apareceu" não tinha como ser afirmado. Simulador que mente faz teste que aprova.
Sabotagem. Toda regra nova é quebrada de propósito depois de escrita, para confirmar que o teste morde. Foi assim que descobri asserções que passavam sem exercitar nada: ok(condição, mensagem) num arquivo cujo helper é ok(mensagem, condição) — a mensagem, sendo texto não vazio, era lida como "verdadeiro" e o bloco inteiro aprovava. E um await numa promessa que nunca resolvia: o teste travava e saía com código de sucesso. Por isso os arquivos têm vigia com prazo, e nenhum espera por promessa que possa pendurar.
Uma verificação de tela.js merece destaque, porque nasceu de um erro meu: todo detector aceso tem de oferecer o seu próprio botão de correção na lista de sugestões. Na v8.37, ao reescrever o bloco das frentes repetidas, apaguei sem querer todos os itens de correção. O botão geral continuou funcionando, nenhum teste caiu, e o usuário simplesmente deixou de ver o que estava errado — a pior classe de defeito, a que não dá sintoma.
O esperado.json congela os números de cada caso conhecido e pega a regressão: o bug que já foi corrigido uma vez e voltou. As duas primeiras regras também rodam dentro do app, na rede de segurança que cancela qualquer correção que fosse perder conteúdo.
pip install -r requirements.txt e dois cliques em iniciar_app.bat. Executável: scripts\build_exe.bat → release\EasyAnkiCards.exe.python src/easyankicards/cli.py examples/exemplo.txt --deck "Meu Baralho".A pasta docs/ é servida pelo GitHub Pages. Ao atualizar, suba os arquivos de docs/ (mantendo o .nojekyll). Como o service worker guarda a versão anterior, na primeira vez após publicar pode ser preciso Ctrl+Shift+R (ou desregistrar o service worker em DevTools → Application) uma única vez; depois, o app avisa das novidades sozinho.
MIT — uso, cópia, modificação e distribuição livres, mantendo o aviso de copyright (veja LICENSE). Contribuições são bem-vindas via pull request.