fix(exercise): persist completion policy and improve dev build workflow (#754)

* fix(exercise): persist completion policy and pass threshold on save

buildExerciseUpdateFields accepted completionPolicy and passThreshold in
the validation schema but never mapped them to the database update, so
exercise settings appeared to save while reverting on refresh.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(db): build workspace deps before compiling

@cio/db imports @cio/utils/notifications, which only exists after
@cio/utils is built. Building db in isolation failed when utils dist
was missing or stale.

- Add a db prebuild step for question-types, utils, and email
- Clean dist and tsbuildinfo before utils and email builds so
  incremental tsc cannot skip emission after dist was removed

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* chore(dev): build api workspace deps before api:dev

Run turbo build for @cio/api and @cio/jobs-worker dependencies before
starting the dev servers, so missing dist/ folders do not break startup.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* feat(course): improve exercise progression grading UX

---------

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
Rotimi Best
2026-07-15 03:26:56 +01:00
committed by GitHub
co-authored by Rotimi Best
parent fdc19aac41
commit bfafc685e0
53 changed files with 2026 additions and 285 deletions
@@ -288,7 +288,8 @@
"saved": "Salvo com sucesso",
"successful_integration": "Integração bem-sucedida",
"successful_deletion": "Integração excluída",
"update_successful": "Atualização bem-sucedida"
"update_successful": "Atualização bem-sucedida",
"unlocked_all_content": "Todos os itens do curso desbloqueados"
}
},
"people": {
@@ -320,7 +321,8 @@
"points_required_auto_grade": "Defina um valor de pontos positivo para cada pergunta antes de salvar.",
"success": "Salvo com sucesso",
"submission_updated": "Envio atualizado para",
"error_fetching": "Falha ao buscar exercícios. Atualize a página"
"error_fetching": "Falha ao buscar exercícios. Atualize a página",
"passed_requires_points": "A conclusão por aprovação precisa de pelo menos um ponto disponível."
},
"poll": {
"info": {
@@ -843,6 +845,12 @@
"title": "E-mail de boas-vindas",
"description": "Personalize o e-mail que os alunos recebem após a inscrição. Adicione uma nota pessoal, instruções ou um link de sessão ao vivo.",
"placeholder": "Escreva a mensagem de boas-vindas que os alunos recebem quando se matriculam…"
},
"locked_content": {
"title": "{count} itens do curso estão bloqueados",
"description": "Os alunos inscritos não poderão fazer aulas ou exercícios bloqueados até que você os desbloqueie.",
"description_live": "Aulas e exercícios bloqueados permanecem ocultos aos alunos até que você os desbloqueie – o que é útil se você liberar conteúdo semana após semana.",
"unlock_all": "Desbloquear tudo"
}
},
"analytics": {
@@ -1410,8 +1418,8 @@
"finish": "Concluir",
"next": "Próximo",
"check": "Verifique",
"auto_graded_badge": "Auto-grade",
"manual_grading_required_badge": "Manual grading required",
"auto_graded_badge": "Avaliação automática",
"manual_grading_required_badge": "Avaliação manual obrigatória",
"points_required_auto_grade": "Points must be greater than 0 for auto-graded exercises.",
"analytics": {
"submissions": "Envios",
@@ -1419,14 +1427,25 @@
"heading": "Individual",
"answers": "Respostas individuais",
"no": "Nenhuma resposta foi fornecida",
"student_avatar": "Estudante"
"student_avatar": "Estudante",
"answers_for": "Respostas de {name}",
"attempts_count": "{count} tentativas",
"attempt_label": "Tentativa {number}",
"date_unknown": "Data desconhecida",
"status_awaiting_manual": "Aguardando avaliação manual",
"status_completed": "Avaliado",
"status_pending": "Pendente",
"counts_as_complete": "Conta como concluído",
"passed": "Aprovado",
"not_passed": "Não aprovado"
},
"summary": {
"heading": "Resumo",
"question_chart": "Gráficos de perguntas",
"responses": "Respostas",
"no_responses": "Ainda não há respostas",
"other": "Outro"
"other": "Outro",
"attempts": "Tentativas"
},
"loading_submissions": "Carregando envios...",
"empty_title": "Ainda não há envios",
@@ -1449,6 +1468,10 @@
"status_pending": "Status: revisão pendente",
"status_graded": "Status: revisão concluída",
"status_submitted": "Status: Enviado",
"passed_completion_result_title": "Aprovado",
"passed_completion_result_description": "Você obteve {scorePercent}%. Este exercício exigia {requiredPercent}% para contar como concluído.",
"not_passed_completion_result_title": "Ainda não aprovado",
"not_passed_completion_result_description": "Você obteve {scorePercent}%. Este exercício exige {requiredPercent}% para contar como concluído.",
"empty_description": "Clique no botão Editar para adicionar.",
"empty_edit_hint_prefix": "Clique no ",
"empty_edit_hint_suffix": " botão para adicionar.",
@@ -1846,6 +1869,30 @@
"sending": "Notificando os alunos…",
"sent": "{count} alunos notificados",
"failed": "Não foi possível notificar os alunos"
},
"zero_points_warning": "Defina pontos acima de 0, caso contrário os alunos não serão avaliados nesta questão.",
"settings_completion_passed_helper": "Os estudantes devem atingir a pontuação de aprovação antes que este exercício conte como concluído.",
"settings_manual_grading_warning": "Envios avaliados manualmente precisam ser avaliados antes de poderem contar como aprovados.",
"settings_single_attempt_warning": "Com uma tentativa, estudantes reprovados precisam de ação do instrutor antes de continuar.",
"settings_existing_submissions_warning": "Alterar os critérios de aprovação pode afetar conclusões e progressões existentes.",
"question_auto_graded_badge": "Avaliação automática",
"question_manual_grading_badge": "Avaliação manual",
"guard": {
"title": "Revise antes de salvar",
"attempts_title": "O que acontece se um aluno for reprovado?",
"description": "Este exercício só conta como concluído quando o aluno é aprovado.",
"retry_option": "Deixe-os tentar novamente até passarem",
"retry_option_description": "Os alunos poderão reenviar quantas vezes forem necessárias.",
"single_attempt_option": "Uma tentativa apenas",
"single_attempt_option_description": "Um aluno reprovado permanece bloqueado até que você altere manualmente o status do envio.",
"recommended": "Recomendado",
"manual_notice_title": "Algumas perguntas precisam de avaliação manual",
"manual_notice_description": "A aprovação de um aluno não é definitiva até que você avalie estas questões:",
"cancel": "Cancelar",
"save_anyway": "Salve mesmo assim",
"save_continue": "Salvar e continuar",
"question_fallback": "Pergunta {number}",
"zero_points_field": "A conclusão aprovada precisa de pelo menos um ponto disponível."
}
},
"new_exercise_modal": {
@@ -2336,7 +2383,8 @@
"later": "Fechar",
"checking": "Verificando elegibilidade…",
"not_eligible_title": "Quase lá",
"retake_exercise": "Refazer o exercício"
"retake_exercise": "Refazer o exercício",
"subtitle_no_certificate": "Parabéns – você concluiu este curso."
}
},
"certificates": {