fix: A38 phase 2 — 0 doublon de fonction globale + garde-fou (v7.34.0)
- Inventaire exhaustif des 13 noms `function NAME` définis 2+ fois (templates + static/js) avec scan de profondeur de brace (strings, comments, backticks gérés) : 12 sont déjà scopés dans des IIFEs depuis A27 (escHtml/flush/emit/setMeta/initials/up/esc/show/close…) — aucun conflit de page possible. - Seul doublon GLOBALE = openCardDetail (corps byte-identiques ×2 dans board_fragment + detailed_board, fragments de vues mutuellement exclusifs) → dédupliquée vers static/js/app.js, 2 copies supprimées ; les onclick/@click des deux fragments appellent la même définition (owner/repo globaux fournis par board.js au moment du clic). - test_no_duplicate_global_functions : garde-fou 0-doublon entre templates et static/js (scanner naïf, plafond ponytail commenté). Reste A38 : méthodes jumelles library/local_workspace (~9-21 noms communs, corps divergents) → fusion workspace-tree.js reportée (réconciliation sans E2E, même logique que A39/A20). suite **1093/1093** · ruff OK · node --check vert · docs à jour
This commit is contained in:
@@ -1,5 +1,32 @@
|
||||
# Changelog - FlowDeck
|
||||
|
||||
## v7.34.0 (2026-10-01) — Audit : A38 phase 2 (0 doublon de fonction globale)
|
||||
|
||||
### Changed
|
||||
|
||||
- **Inventaire exhaustif de la duplication de fonctions** : scan de
|
||||
profondeur de brace sur les 13 noms définis 2+ fois (templates +
|
||||
static/js, strings/comments/backticks gérés) → **12 sont déjà scopés**
|
||||
dans des IIFEs depuis A27 (aucun conflit de page possible)
|
||||
- **Seul doublon global = `openCardDetail`** (corps byte-identiques ×2 dans
|
||||
`board_fragment` + `detailed_board`, fragments de vues mutuellement
|
||||
exclusifs) → dédupliquée vers `static/js/app.js`, les 2 copies
|
||||
supprimées (les onclick/@click des deux fragments appellent la même
|
||||
définition)
|
||||
|
||||
### Tests
|
||||
|
||||
- `test_no_duplicate_global_functions` : garde-fou — 0 `function NAME`
|
||||
globale définie 2+ fois entre templates et static/js (scanner naïf,
|
||||
plafond `ponytail:` commenté : un faux positif se lit au nom signalé)
|
||||
- Suite complète : **1092/1092** · ruff OK · node --check vert
|
||||
|
||||
### Notes
|
||||
|
||||
- Reste A38 : méthodes jumelles library/local_workspace (~9-21 noms
|
||||
communs, corps divergents) → fusion `workspace-tree.js` reportée
|
||||
(réconciliation sans E2E — même logique que A39/A20)
|
||||
|
||||
## v7.33.0 (2026-10-01) — Audit : A43 TERMINÉ + A38 phase 1 (CSRF unifié)
|
||||
|
||||
### Fixed
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,6 +1,6 @@
|
||||
# WORKLOAD — FlowDeck Notion Clone
|
||||
|
||||
> **Début**: 2026-07-08 | **Version**: v7.33.0 (audit — A43 TERMINÉ + A38 phase 1 CSRF : 76 lectures → getCsrf() | **Statut**: EN COURS 🔄
|
||||
> **Début**: 2026-07-08 | **Version**: v7.34.0 (audit — A38 phase 2 : 0 doublon de fonction globale, garde-fou testé | **Statut**: EN COURS 🔄
|
||||
> **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0`
|
||||
|
||||
## Avancement Global
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI):
|
||||
|
||||
app = FastAPI(
|
||||
title="FlowDeck",
|
||||
version="7.33.0",
|
||||
version="7.34.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
lifespan=lifespan,
|
||||
|
||||
@@ -49,12 +49,5 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script nonce="{{ csp_nonce() }}">
|
||||
function openCardDetail(id) {
|
||||
htmx.ajax('GET', `/api/issues/${owner}/${repo}/${id}?format=html`, {
|
||||
target: '#card-modal-content', swap: 'innerHTML'
|
||||
});
|
||||
document.getElementById('card-modal').style.display = 'flex';
|
||||
document.getElementById('card-modal-title').textContent = 'Issue #' + id;
|
||||
}
|
||||
</script>
|
||||
<!-- A38 : openCardDetail vit dans static/js/app.js (dedupliquee,
|
||||
corps identique x2 dans ces deux fragments de vue) -->
|
||||
|
||||
@@ -58,12 +58,5 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script nonce="{{ csp_nonce() }}">
|
||||
function openCardDetail(id) {
|
||||
htmx.ajax('GET', `/api/issues/${owner}/${repo}/${id}?format=html`, {
|
||||
target: '#card-modal-content', swap: 'innerHTML'
|
||||
});
|
||||
document.getElementById('card-modal').style.display = 'flex';
|
||||
document.getElementById('card-modal-title').textContent = 'Issue #' + id;
|
||||
}
|
||||
</script>
|
||||
<!-- A38 : openCardDetail vit dans static/js/app.js (dedupliquee,
|
||||
corps identique x2 dans ces deux fragments de vue) -->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FlowDeck",
|
||||
"version": "7.33.0"
|
||||
"version": "7.34.0"
|
||||
},
|
||||
"paths": {
|
||||
"/auth/register": {
|
||||
|
||||
@@ -395,3 +395,15 @@
|
||||
});
|
||||
})();
|
||||
|
||||
// A38 : openCardDetail était définie (corps identique) dans board_fragment
|
||||
// ET detailed_board — une seule définition globale ici, appelée par les
|
||||
// onclick/@click des deux fragments (owner/repo globaux fournis par board.js).
|
||||
function openCardDetail(id) {
|
||||
htmx.ajax('GET', `/api/issues/${owner}/${repo}/${id}?format=html`, {
|
||||
target: '#card-modal-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
document.getElementById('card-modal').style.display = 'flex';
|
||||
document.getElementById('card-modal-title').textContent = 'Issue #' + id;
|
||||
}
|
||||
|
||||
|
||||
@@ -366,6 +366,73 @@ def test_csrf_server_rendered_no_placeholder(client):
|
||||
assert token == cookie, (token[:8], cookie[:8])
|
||||
|
||||
|
||||
def test_no_duplicate_global_functions():
|
||||
"""A38 : aucune fonction `function NAME` GLOBALE (profondeur 0) définie
|
||||
2+ fois entre les templates et static/js — les paires à risque d'ombre
|
||||
silencieuse (onDoc, escHtml, openCardDetail…) vivent dans des IIFEs ou
|
||||
sont dédupliquées (openCardDetail → app.js)."""
|
||||
import pathlib as _pathlib
|
||||
import re as _re
|
||||
|
||||
files = list(_pathlib.Path("app/templates").glob("*.html")) + list(
|
||||
_pathlib.Path("static/js").glob("*.js")
|
||||
)
|
||||
found: dict[str, list[str]] = {}
|
||||
for p in files:
|
||||
s = p.read_text(encoding="utf-8", errors="ignore")
|
||||
depth = 0
|
||||
line = 1
|
||||
i = 0
|
||||
state = None
|
||||
n = len(s)
|
||||
# ponytail: scanner naïve (strings/comments/backticks) — un faux
|
||||
# positif se voit immédiatement à la lecture du nom signalé
|
||||
while i < n:
|
||||
c = s[i]
|
||||
if c == "\n":
|
||||
line += 1
|
||||
if state is None:
|
||||
if c in ('"', "'"):
|
||||
state = c
|
||||
i += 1
|
||||
continue
|
||||
if c == "`":
|
||||
state = c
|
||||
i += 1
|
||||
continue
|
||||
if c == "/" and i + 1 < n and s[i + 1] == "/":
|
||||
state = "//"
|
||||
i += 2
|
||||
continue
|
||||
if c == "/" and i + 1 < n and s[i + 1] == "*":
|
||||
state = "/*"
|
||||
i += 2
|
||||
continue
|
||||
if c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
else:
|
||||
if c == "\\":
|
||||
i += 2
|
||||
continue
|
||||
if (state in ('"', "'") and c == state) or (state == "`" and c == state):
|
||||
state = None
|
||||
elif state == "//" and c == "\n":
|
||||
state = None
|
||||
elif state == "/*" and c == "*" and i + 1 < n and s[i + 1] == "/":
|
||||
state = None
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
if state is None and depth == 0 and s.startswith("function ", i):
|
||||
m = _re.match(r"function\s+([A-Za-z_]\w*)", s[i : i + 60])
|
||||
if m:
|
||||
found.setdefault(m.group(1), []).append(f"{p.name}:{line}")
|
||||
dups = {k: v for k, v in found.items() if len(v) >= 2}
|
||||
assert dups == {}, dups
|
||||
|
||||
|
||||
def test_no_duplicate_routes():
|
||||
"""A24 : deux routes même méthode+chemin → l'une écrase silencieusement l'autre."""
|
||||
from app.main import app
|
||||
|
||||
Reference in New Issue
Block a user