Corrige deux régressions de /local-workspace :
- le chemin du header restait bloqué sur « Home / <workspace> » quel que
soit le dossier affiché : la route rend désormais breadcrumb_items
(Home / <workspace> / <dossier> / <sous-dossier>, niveaux cliquables,
collapse « … » au-delà de 4) et la navigation sans rechargement recalcule
le chemin via l'event flowdeck:breadcrumb-changed ;
- le clic sur un dossier du sidebar affichait TOUS les composants à la
fois : Alpine.data('wsInitData') retournait le même objet singleton, le
2e montage (navigation partielle) levait « Cannot redefine property:
\ » et initTree abandonnait, laissant tout le contenu au state
brut. La factory retourne désormais une enveloppe fraîche par montage
qui délègue à l'état réactif partagé. #lw-config est aussi relu à chaque
exécution (le 2e montage gardait le folder_id du 1er chargement).
Inclus également le travail en cours de l'arbre : Library (colonnes Last
visited/Source, ordre d'en-tête, favoris à icônes Workspace), Meeting
Notes (bloc, CSS, routes, docs), coloration de code hljs, badges
favori/publié dans l'arbre local-workspace, docs (DATA_MODEL,
architectures) et tests associés.
- app.js : x-ignore sur .main-wrapper au swap fdLoad (nœud remplacé
uniquement), démontage unique quand tous les <script src> ont exécuté
(load/error + filet 4s) via Alpine.initTree idempotent (_x_marker) —
fini les cascades « Undefined variable » (éditeur) et « reading 'has' »
($store.fdCtx) : la zone s'initialisait avant composants et stores.
- 5 scripts de page gardés contre la ré-exécution (SyntaxError
« Identifier 'LW' has already been declared » à la 2e visite
partielle) : local_workspace, board, settings, database_table,
page_editor_realtime.
- if (window.Alpine) → Alpine.data immédiat sinon listener alpine:init
(déjà passé sur swap) : 8 scripts + 7 templates inline.
- app.css : purge des 7 blocs @font-face Inter orphelins (fichiers
inexistants → 302 HTML → « Failed to decode downloaded font »).
- Gates : e2e/regression_editor_mount + e2e/regression_partial_nav
(7 pages x complet/1er/2e passage) + regression_logout_dnd + smoke
= 6/6 verts sur l'image rebuildée · pytest 1094 passed · ruff OK ·
OpenAPI 7.45.2.
A43 (les 4 items sont clos) :
- `__CSRF_PLACEHOLDER__` supprimé : ContextVar CSRF_TOKEN posée par
CSRFMiddleware avant call_next (même mécanique que le nonce CSP),
global `{{ csrf_token() }}` dans templating, base.html rend
`{"X-CSRF-Token":{{ csrf_token()|tojson }}}` — vide si cookie absent,
`htmx:configRequest` re-lit le cookie à chaque appel → plus jamais de
jeton factice servi.
- Palette : `fetch('/api/search…')` SANS header (GET ∈ SAFE_METHODS →
CSRF inapplicable) — le JSON.parse du body-attr par frappe disparaît.
- utcnow déprécié = 0 dans app/**.py ; health loggé (A25) + booléens db/
gitea = raison ; probe réseau = voulu (test de connectivité).
A38 phase 1 (CSRF unifié) :
- `window.getCsrf()` unique dans le <head> de base.html.
- 76 lectures brutes du cookie → getCsrf() dans 13 fichiers (47 formes
`(…||[])[1]||''`, 25 déclarations `const X = match(…)` avec conversion
de leurs usages `X?X[1]:''` → `X` (noms collectés PAR FICHIER, jamais
de règle globale), 4 formes espacées).
- Définitions dupliquées supprimées : card_detail.html (multi-lignes),
database_table.js (1 ligne dans IIFE → chute sur le global).
- Les 3 variantes de base.html (IIFE getCsrf + 2 getCsrfToken) →
`return getCsrf()`.
- welcome.html garde sa lecture locale (page autonome sans base) ;
reste exactement 1 raw dans base.html (la déf head) + 2 dans welcome.
Test : test_csrf_server_rendered_no_placeholder (pas de placeholder +
token du hx-headers == cookie csrf_token).
suite **1092/1092** · ruff OK · node --check vert · docs à jour
Extraction des 7 templates dont le JS n'est PAS interpolé Jinja → 9 fichiers
static/js/*.js (4 243 lignes, -30 % du JS inline : 13 904 → 9 661) :
- agent_panel_1/_2 (bloc de 1 788 L livré sur CHAQUE page), library (1 039),
gitea_workspace (626), _icon_picker_1/_2, _ctx_menu, import, workspaces
- UN fichier par bloc : ordre/timing identiques (pas de defer, attributs
conservés dont data-cfasync), cache-busting via ?v={{ asset_version }}
(source unique A40), scripts externes = 'self' en CSP (pas de nonce requis)
- garde-fou : le script refuse tout bloc contenant {{ ou {%
- vérifs : node --check vert sur les 9, 0 script inline restant dans les
cibles, suite complète 1089/1089
Lint (la moitié « ajouter les templates à eslint » de l'audit) :
- eslint.config.mjs existait (flat v9, sans dépendances npm) mais AUCUN
binaire eslint n'était installé → npm i -g eslint
- `eslint static/js` → 0 erreur, 120 warnings (no-unused-vars 69,
no-empty 36, no-undef 15) sur 8 fichiers = baseline à nettoyer
- les extraits sont couverts d'office par la config (static/js/**/*.js)
Reste A27 : blocs interpolés Jinja (page_editor 2 517, local_workspace 2 031,
base 1 523, database_table 1 323, settings 1 093, realtime 531 ≈ 9 661 L)
→ extraction en 2 temps (config JSON injectée + script statique).
suite **1089/1089** · ruff OK · docs à jour