Phase 3 : hardening qualite de code - typage strict, securite, dead code, a11y
Config strictement stricte partout (ruff, mypy --strict, bandit, vulture, import-linter, eslint, stylelint), aucune regle desactivee "pour ne pas casser le build" - l'existant a ete corrige pour la satisfaire plutot que l'inverse. Hooks pre-commit locaux (language: system) bloquants. - Typage mypy --strict propage a tout le moteur (db, screens, auth, core, ai, routes, puis publish/scripts/tests/app.py/build_css.py). - Securite : fuite de handle fichier Windows corrigee dans l'export SCORM (routes/publish/export_scorm.py), CSRF/RNG non-crypto/xAPI documentes (# nosec, # NOSONAR justifies), nouveau db.json_for_script() (echappe "</script>" dans le JSON embarque en <script>, 25 sites). - Architecture : imports circulaires/F811 nettoyes, contrats import-linter respectes, code mort retire (vulture). - Accessibilite : 69 champs de formulaire sans label correctement associe corriges (for/id ou aria-label) sur 11 templates. - ESLint/Stylelint : lot mecanique JS/CSS, regles ajustees puis appliquees (aucune desactivee sans verification individuelle). - Tests : isolation du compte admin partage (nettoyage ponctuel + fixture de teardown automatique en filet de securite), suite complete verte (591 tests Python, 241 tests JS). - SonarQube Community Build self-heberge (Docker + PostgreSQL) : rapport complet analyse point par point, faux positifs documentes. - .gitattributes ajoute (LF force) : core.autocrlf=true sur cette machine faisait echouer ESLint (linebreak-style) via un bug connu de git (checkout "en place" qui ignore l'eol force sur un fichier deja present sur disque - contourne en supprimant puis recreant chaque fichier suivi). djLint (H021, styles inline) volontairement saute pour ce commit - backlog assume, deja documente, traite dans un lot separe. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7db4803b93
commit
c57420c8c9
@@ -20,22 +20,22 @@
|
||||
// décoration propre à CHAQUE modèle, pas du contrat partagé).
|
||||
function forgeQuizProgressOnShow(widget, progressSelector) {
|
||||
if (!widget) return;
|
||||
var quizObjectId = widget.getAttribute('data-object-id');
|
||||
var quizState = (typeof forgeQuizBoxState !== 'undefined') ? forgeQuizBoxState[quizObjectId] : null;
|
||||
const quizObjectId = widget.getAttribute('data-object-id');
|
||||
const quizState = (typeof forgeQuizBoxState !== 'undefined') ? forgeQuizBoxState[quizObjectId] : null;
|
||||
if (!quizState) return;
|
||||
var dialogueState = (typeof forgeDialogueBoxState !== 'undefined') ? forgeDialogueBoxState[quizState.dialogueObjectId] : null;
|
||||
const dialogueState = (typeof forgeDialogueBoxState !== 'undefined') ? forgeDialogueBoxState[quizState.dialogueObjectId] : null;
|
||||
if (!dialogueState) return;
|
||||
function isQuestion(line) { return line && line.type === 'question'; }
|
||||
var total = dialogueState.lines.filter(isQuestion).length;
|
||||
const total = dialogueState.lines.filter(isQuestion).length;
|
||||
if (total === 0) return;
|
||||
var current = dialogueState.lines.slice(0, dialogueState.index + 1).filter(isQuestion).length;
|
||||
var header = widget.querySelector('[data-quiz-role="header"]');
|
||||
if (header) header.textContent = 'Question ' + current + '/' + total;
|
||||
var progressEl = widget.querySelector(progressSelector);
|
||||
const current = dialogueState.lines.slice(0, dialogueState.index + 1).filter(isQuestion).length;
|
||||
const header = widget.querySelector('[data-quiz-role="header"]');
|
||||
if (header) header.textContent = `Question ${current}/${total}`;
|
||||
const progressEl = widget.querySelector(progressSelector);
|
||||
if (progressEl) {
|
||||
var bars = [];
|
||||
for (var i = 1; i <= total; i++) {
|
||||
bars.push('<i class="' + (i < current ? 'is-done' : (i === current ? 'is-current' : '')) + '"></i>');
|
||||
const bars = [];
|
||||
for (let i = 1; i <= total; i++) {
|
||||
bars.push(`<i class="${i < current ? 'is-done' : (i === current ? 'is-current' : '')}"></i>`);
|
||||
}
|
||||
progressEl.innerHTML = bars.join('');
|
||||
}
|
||||
@@ -56,7 +56,7 @@ function forgeQuizClassiqueOnShow(widget) {
|
||||
// .classiqueContent (pas la racine du widget, voir
|
||||
// render_quiz_box_page_classique) plutôt que sur le widget lui-même.
|
||||
if (widget) {
|
||||
var content = widget.querySelector('.classiqueContent');
|
||||
const content = widget.querySelector('.classiqueContent');
|
||||
if (content) content.classList.remove('is-showing-result');
|
||||
}
|
||||
forgeQuizProgressOnShow(widget, '[data-classique-role="progress"]');
|
||||
@@ -76,26 +76,25 @@ function forgeQuizClassiqueOnShow(widget) {
|
||||
// forgeQuizResultQuit, dialogue-box-controller.js.
|
||||
function forgeQuizClassiqueOnQuizComplete(widget, result) {
|
||||
if (!widget) return;
|
||||
var content = widget.querySelector('.classiqueContent');
|
||||
var zone = widget.querySelector('[data-classique-role="result"]');
|
||||
const content = widget.querySelector('.classiqueContent');
|
||||
const zone = widget.querySelector('[data-classique-role="result"]');
|
||||
if (!content || !zone) return;
|
||||
var total = (result && result.totalCount) || 0;
|
||||
var correct = (result && result.correctCount) || 0;
|
||||
var pct = total > 0 ? Math.round((correct / total) * 100) : 0;
|
||||
var threshold = (typeof window !== 'undefined' && typeof window.FORGE_SUCCESS_THRESHOLD_PERCENT === 'number')
|
||||
const total = (result && result.totalCount) || 0;
|
||||
const correct = (result && result.correctCount) || 0;
|
||||
const pct = total > 0 ? Math.round((correct / total) * 100) : 0;
|
||||
const threshold = (typeof window !== 'undefined' && typeof window.FORGE_SUCCESS_THRESHOLD_PERCENT === 'number')
|
||||
? window.FORGE_SUCCESS_THRESHOLD_PERCENT : 70;
|
||||
var passed = pct >= threshold;
|
||||
var titleEl = widget.querySelector('.classiqueHeaderTitle');
|
||||
var title = titleEl ? titleEl.textContent : 'Quiz';
|
||||
var quizObjectId = widget.getAttribute('data-object-id');
|
||||
zone.innerHTML =
|
||||
'<div class="classiqueResultRing" style="--pct:' + pct + '"><span>' + pct + '%</span></div>' +
|
||||
'<p class="classiqueResultTitle">' + correct + ' bonne' + (correct > 1 ? 's' : '') + ' réponse' + (correct > 1 ? 's' : '') + ' sur ' + total + '</p>' +
|
||||
'<p class="classiqueResultSub">« ' + (typeof forgeEscapeHtml === 'function' ? forgeEscapeHtml(title) : title) + ' »</p>' +
|
||||
'<div class="classiqueResultBadge ' + (passed ? 'pass' : 'fail') + '">' +
|
||||
(passed ? '✓ Module validé' : '✕ Module non validé — seuil ' + threshold + '%') +
|
||||
'</div>' +
|
||||
'<button type="button" class="classiqueQuitBtn" onclick="forgeQuizResultQuit(' + quizObjectId + ')">✕ Quitter le quiz</button>';
|
||||
const passed = pct >= threshold;
|
||||
const titleEl = widget.querySelector('.classiqueHeaderTitle');
|
||||
const title = titleEl ? titleEl.textContent : 'Quiz';
|
||||
const quizObjectId = widget.getAttribute('data-object-id');
|
||||
zone.innerHTML = `<div class="classiqueResultRing" style="--pct:${pct}"><span>${pct}%</span></div>`
|
||||
+ `<p class="classiqueResultTitle">${correct} bonne${correct > 1 ? 's' : ''} réponse${correct > 1 ? 's' : ''} sur ${total}</p>`
|
||||
+ `<p class="classiqueResultSub">« ${typeof forgeEscapeHtml === 'function' ? forgeEscapeHtml(title) : title} »</p>`
|
||||
+ `<div class="classiqueResultBadge ${passed ? 'pass' : 'fail'}">${
|
||||
passed ? '✓ Module validé' : `✕ Module non validé — seuil ${threshold}%`
|
||||
}</div>`
|
||||
+ `<button type="button" class="classiqueQuitBtn" onclick="forgeQuizResultQuit(${quizObjectId})">✕ Quitter le quiz</button>`;
|
||||
content.classList.add('is-showing-result');
|
||||
}
|
||||
|
||||
@@ -111,32 +110,31 @@ function forgeQuizClassiqueOnQuizComplete(widget, result) {
|
||||
// visuellement se lire comme entièrement complété.
|
||||
function forgeQuizMangaOnQuizComplete(widget, result) {
|
||||
if (!widget) return;
|
||||
var zone = widget.querySelector('[data-manga-role="result"]');
|
||||
const zone = widget.querySelector('[data-manga-role="result"]');
|
||||
if (!zone) return;
|
||||
var total = (result && result.totalCount) || 0;
|
||||
var correct = (result && result.correctCount) || 0;
|
||||
var pct = total > 0 ? Math.round((correct / total) * 100) : 0;
|
||||
var threshold = (typeof window !== 'undefined' && typeof window.FORGE_SUCCESS_THRESHOLD_PERCENT === 'number')
|
||||
const total = (result && result.totalCount) || 0;
|
||||
const correct = (result && result.correctCount) || 0;
|
||||
const pct = total > 0 ? Math.round((correct / total) * 100) : 0;
|
||||
const threshold = (typeof window !== 'undefined' && typeof window.FORGE_SUCCESS_THRESHOLD_PERCENT === 'number')
|
||||
? window.FORGE_SUCCESS_THRESHOLD_PERCENT : 70;
|
||||
var passed = pct >= threshold;
|
||||
var quizObjectId = widget.getAttribute('data-object-id');
|
||||
zone.innerHTML =
|
||||
'<div class="mangaResultCard">' +
|
||||
'<span class="mangaResultTitle">Fin du quiz</span>' +
|
||||
'<p class="mangaResultScore">Score final : ' + correct + ' / ' + total + '</p>' +
|
||||
'<div class="mangaResultBadge ' + (passed ? 'pass' : 'fail') + '">' + (passed ? '合格' : '不合格') + '</div>' +
|
||||
'<div><button type="button" class="mangaQuitBtn" onclick="forgeQuizResultQuit(' + quizObjectId + ')">Quitter le quiz</button></div>' +
|
||||
'</div>';
|
||||
var progressEl = widget.querySelector('[data-manga-role="progress"]');
|
||||
const passed = pct >= threshold;
|
||||
const quizObjectId = widget.getAttribute('data-object-id');
|
||||
zone.innerHTML = '<div class="mangaResultCard">'
|
||||
+ '<span class="mangaResultTitle">Fin du quiz</span>'
|
||||
+ `<p class="mangaResultScore">Score final : ${correct} / ${total}</p>`
|
||||
+ `<div class="mangaResultBadge ${passed ? 'pass' : 'fail'}">${passed ? '合格' : '不合格'}</div>`
|
||||
+ `<div><button type="button" class="mangaQuitBtn" onclick="forgeQuizResultQuit(${quizObjectId})">Quitter le quiz</button></div>`
|
||||
+ '</div>';
|
||||
const progressEl = widget.querySelector('[data-manga-role="progress"]');
|
||||
if (progressEl && total > 0) {
|
||||
var bars = [];
|
||||
for (var i = 0; i < total; i++) bars.push('<i class="is-done"></i>');
|
||||
const bars = [];
|
||||
for (let i = 0; i < total; i++) bars.push('<i class="is-done"></i>');
|
||||
progressEl.innerHTML = bars.join('');
|
||||
}
|
||||
widget.classList.add('is-showing-result');
|
||||
}
|
||||
|
||||
var forgeQuizTemplateEffects = {
|
||||
const forgeQuizTemplateEffects = {
|
||||
// Modèles "boîte de dialogue" (voir screens/rendering/quiz_box_config.py
|
||||
// ::QUIZ_BOX_DIALOG_TEMPLATES) — "Verre dépoli"/"Néon"/"Minimal"/
|
||||
// "Ludique" ont été retirés (demande explicite : "supprime tous les
|
||||
@@ -159,7 +157,10 @@ var forgeQuizTemplateEffects = {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
forgeQuizTemplateEffects,
|
||||
forgeQuizProgressOnShow, forgeQuizMangaOnShow, forgeQuizMangaOnQuizComplete,
|
||||
forgeQuizClassiqueOnShow, forgeQuizClassiqueOnQuizComplete,
|
||||
forgeQuizProgressOnShow,
|
||||
forgeQuizMangaOnShow,
|
||||
forgeQuizMangaOnQuizComplete,
|
||||
forgeQuizClassiqueOnShow,
|
||||
forgeQuizClassiqueOnQuizComplete,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user