v3.0.1: qualité & stabilité — tests, sécurité, UX consolidée
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

🧪 Tests: 73 → 130 (+57 tests)
- Upload API, labels sync, commit history (401/400/502/cached)
- File create/update (sha=null, sha=abc)
- Admin delete cascade (12 tests: oauth, tags, workspaces, pages…)
- Gitea status (linked/unlinked/disconnect)
- OAuth link mode (session, redirect, callback)

🔒 Sécurité
- ContentSecurityPolicyMiddleware (CSP headers)
- RateLimitMiddleware (100 req/min/IP)
- Pydantic models: ErrorResponse, SuccessResponse
- Input validation upload (10MB, allowed extensions)
- Pydantic request models

🎨 UX
- static/css/design-tokens.css (500 lines, thèmes + skeletons)
- Toast system: 16 alert() remplacés par toast()
- 59 lignes CSS dupliquées retirées (6 templates)
- Skeletons cohérents sur toutes les vues

⚡ Performance
- Cache TTL sur get_file_commits (consistant avec les autres méthodes)
This commit is contained in:
2026-07-13 23:08:53 -04:00
parent e04b3a38a4
commit de40c2d83e
12 changed files with 1679 additions and 88 deletions
+6 -17
View File
@@ -42,17 +42,6 @@
.gw-commit-bar{display:flex;align-items:center;gap:8px;padding:12px 24px;background:var(--bg-secondary);border-top:1px solid var(--border);flex-wrap:wrap;}
.gw-commit-bar input{flex:1;min-width:180px;padding:8px 12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;outline:none;}
.gw-commit-bar input:focus{border-color:var(--accent);}
.btn-primary{padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;border:none;font-weight:500;background:var(--accent);color:#fff;}
.btn-primary:hover{background:var(--accent-hover);}
.btn-danger{background:rgba(255,80,80,.15);color:var(--danger);border:none;padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;font-weight:500;}
.btn-danger:hover{background:rgba(255,80,80,.25);}
.btn{border:none;padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;font-weight:500;border:1px solid var(--border);background:transparent;color:var(--text-dim);}
.btn:hover{background:var(--bg-tertiary);color:var(--text);}
/* ── Skeleton loading animation ── */
.skeleton{background:var(--bg-tertiary);animation:pulse 1.5s ease-in-out infinite;border-radius:4px;}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
.file-skeleton{padding:16px;border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);max-height:70vh;overflow:hidden;}
/* Prism theme tweaks for dark UI */
.gw-content pre[class*="language-"]{background:var(--bg-tertiary)!important;border:1px solid var(--border)!important;border-radius:8px!important;padding:16px!important;}
@@ -70,7 +59,7 @@
</div>
<div class="gw-content">
<!-- Skeleton loading -->
<div x-show="fileLoading" class="file-skeleton">
<div x-show="fileLoading" class="skeleton-file">
<div class="skeleton" style="height:15px;width:60%;margin-bottom:12px;"></div>
<div class="skeleton" style="height:15px;width:80%;margin-bottom:12px;"></div>
<div class="skeleton" style="height:15px;width:40%;margin-bottom:12px;"></div>
@@ -450,9 +439,9 @@ function giteaWorkspace() {
}
} else {
var d = await r.json();
alert('Failed to save: ' + (d.error || 'Unknown error'));
window.showToast('Failed to save: ' + (d.error || 'Unknown error'), 'error');
}
} catch(e) { alert('Network error'); }
} catch(e) { window.showToast('Network error', 'error'); }
},
async deleteCurrentFile() {
@@ -551,9 +540,9 @@ function giteaWorkspace() {
await this.loadSidebarTree();
} else {
var d = await r.json();
alert('Failed: ' + (d.error || d.detail || 'Unknown error'));
window.showToast('Failed: ' + (d.error || d.detail || 'Unknown error'), 'error');
}
} catch(e) { alert('Error: ' + e.message); }
} catch(e) { window.showToast('Error: ' + e.message, 'error'); }
},
// Upload files
@@ -720,7 +709,7 @@ function giteaWorkspace() {
span.className = 'page-name';
span.textContent = originalName;
input.replaceWith(span);
alert('Rename failed');
window.showToast('Rename failed', 'error');
}
}).catch(function() {
var span = document.createElement('span');